aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile10
-rw-r--r--config.ld2
-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
-rw-r--r--luasystem-scm-0.rockspec1
-rw-r--r--spec/04-term_spec.lua20
-rw-r--r--spec/05-bitflags_spec.lua108
-rw-r--r--src/bitflags.c235
-rw-r--r--src/bitflags.h21
-rw-r--r--src/compat.h11
-rw-r--r--src/core.c2
-rw-r--r--src/term.c822
-rw-r--r--system/init.lua212
17 files changed, 1794 insertions, 8 deletions
diff --git a/Makefile b/Makefile
index 4f4d685..dd20ee9 100644
--- a/Makefile
+++ b/Makefile
@@ -45,3 +45,13 @@ install-all:
45 @cd src && $(MAKE) install LUA_VERSION=5.3 45 @cd src && $(MAKE) install LUA_VERSION=5.3
46 46
47.PHONY: test 47.PHONY: test
48test:
49 busted
50
51.PHONY: lint
52lint:
53 luacheck .
54
55.PHONY: docs
56docs:
57 ldoc .
diff --git a/config.ld b/config.ld
index c13936d..c96d01d 100644
--- a/config.ld
+++ b/config.ld
@@ -8,7 +8,7 @@ style="./doc_topics/"
8 8
9file={'./src/', './system/'} 9file={'./src/', './system/'}
10topics={'./doc_topics/', './LICENSE.md', './CHANGELOG.md'} 10topics={'./doc_topics/', './LICENSE.md', './CHANGELOG.md'}
11-- examples = {'./examples'} 11examples = {'./examples'}
12 12
13dir='docs' 13dir='docs'
14sort=true 14sort=true
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 @@
1-- This example shows how to remove platform differences to create a
2-- cross-platform level playing field.
3
4local sys = require "system"
5
6
7
8if sys.is_windows then
9 -- Windows holds multiple copies of environment variables, to ensure `getenv`
10 -- returns what `setenv` sets we need to use the `system.getenv` instead of
11 -- `os.getenv`.
12 os.getenv = sys.getenv -- luacheck: ignore
13
14 -- Set up the terminal to handle ANSI escape sequences on Windows.
15 if sys.isatty(io.stdout) then
16 sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
17 end
18 if sys.isatty(io.stderr) then
19 sys.setconsoleflags(io.stderr, sys.getconsoleflags(io.stderr) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
20 end
21 if sys.isatty(io.stdin) then
22 sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdout) + sys.ENABLE_VIRTUAL_TERMINAL_INPUT)
23 end
24
25
26else
27 -- On Posix, one can set a variable to an empty string, but on Windows, this
28 -- will remove the variable from the environment. To make this consistent
29 -- across platforms, we will remove the variable from the environment if the
30 -- value is an empty string.
31 local old_setenv = sys.setenv
32 function sys.setenv(name, value)
33 if value == "" then value = nil end
34 return old_setenv(name, value)
35 end
36end
37
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 @@
1local sys = require "system"
2
3-- Print the Windows Console flags for stdin
4sys.listconsoleflags(io.stdin)
5
6-- Print the Posix termios flags for stdin
7sys.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 @@
1local sys = require "system"
2
3print [[
4
5This example shows how to disable the "echo" of characters read to the console,
6useful for reading secrets from the user.
7
8]]
9
10--- Function to read from stdin without echoing the input (for secrets etc).
11-- It will (in a platform agnostic way) disable echo on the terminal, read the
12-- input, and then re-enable echo.
13-- @param ... Arguments to pass to `io.stdin:read()`
14-- @return the results of `io.stdin:read(...)`
15local function read_secret(...)
16 local w_oldflags, p_oldflags
17
18 if sys.isatty(io.stdin) then
19 -- backup settings, configure echo flags
20 w_oldflags = sys.getconsoleflags(io.stdin)
21 p_oldflags = sys.tcgetattr(io.stdin)
22 -- set echo off to not show password on screen
23 assert(sys.setconsoleflags(io.stdin, w_oldflags - sys.CIF_ECHO_INPUT))
24 assert(sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = p_oldflags.lflag - sys.L_ECHO }))
25 end
26
27 local secret, err = io.stdin:read(...)
28
29 -- restore settings
30 if sys.isatty(io.stdin) then
31 io.stdout:write("\n") -- Add newline after reading the password
32 sys.setconsoleflags(io.stdin, w_oldflags)
33 sys.tcsetattr(io.stdin, sys.TCSANOW, p_oldflags)
34 end
35
36 return secret, err
37end
38
39
40
41-- Get username
42io.write("Username: ")
43local username = io.stdin:read("*l")
44
45-- Get the secret
46io.write("Password: ")
47local password = read_secret("*l")
48
49-- Get domainname
50io.write("Domain : ")
51local domain = io.stdin:read("*l")
52
53
54-- Print the results
55print("")
56print("Here's what we got:")
57print(" username: " .. username)
58print(" password: " .. password)
59print(" 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 @@
1local sys = require "system"
2
3print [[
4
5This example shows how to do a non-blocking read from the cli.
6
7]]
8
9-- setup Windows console to handle ANSI processing
10local of_in = sys.getconsoleflags(io.stdin)
11local of_out = sys.getconsoleflags(io.stdout)
12sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
13sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT)
14
15-- setup Posix terminal to use non-blocking mode, and disable line-mode
16local of_attr = sys.tcgetattr(io.stdin)
17local of_block = sys.getnonblock(io.stdin)
18sys.setnonblock(io.stdin, true)
19sys.tcsetattr(io.stdin, sys.TCSANOW, {
20 lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, -- disable canonical mode and echo
21})
22
23-- cursor sequences
24local get_cursor_pos = "\27[6n"
25
26
27
28local read_input do
29 local left_over_key
30
31 -- Reads a single key, if it is a 27 (start of ansi escape sequence) then it reads
32 -- the rest of the sequence.
33 -- This function is non-blocking, and will return nil if no key is available.
34 -- In case of an ANSI sequence, it will return the full sequence as a string.
35 -- @return nil|string the key read, or nil if no key is available
36 function read_input()
37 if left_over_key then
38 -- we still have a cached key, return it
39 local key = left_over_key
40 left_over_key = nil
41 return string.char(key)
42 end
43
44 local key = sys.readkey()
45 if key == nil then
46 return nil
47 end
48
49 if key ~= 27 then
50 return string.char(key)
51 end
52
53 -- looks like an ansi escape sequence, immediately read next char
54 -- as an heuristic against manually typing escape sequences
55 local brack = sys.readkey()
56 if brack ~= 91 then
57 -- not the expected [ character, so we return the key as is
58 -- and store the extra key read for the next call
59 left_over_key = brack
60 return string.char(key)
61 end
62
63 -- escape sequence detected, read the rest of the sequence
64 local seq = { key, brack }
65 while true do
66 key = sys.readkey()
67 table.insert(seq, key)
68 if (key >= 65 and key <= 90) or (key >= 97 and key <= 126) then
69 -- end of sequence, return the full sequence
70 return string.char((unpack or table.unpack)(seq))
71 end
72 end
73 -- unreachable
74 end
75end
76
77
78
79print("Press a key, or 'A' to get cursor position, 'ESC' to exit")
80while true do
81 local key
82
83 -- wait for a key, and sleep a bit to not do a busy-wait
84 while not key do
85 key = read_input()
86 if not key then sys.sleep(0.1) end
87 end
88
89 if key == "A" then io.write(get_cursor_pos); io.flush() end
90
91 -- check if we got a key or ANSI sequence
92 if #key == 1 then
93 -- just a key
94 local b = key:byte()
95 if b < 32 then
96 key = "." -- replace control characters with a simple "." to not mess up the screen
97 end
98
99 print("you pressed: " .. key .. " (" .. b .. ")")
100 if b == 27 then
101 print("Escape pressed, exiting")
102 break
103 end
104
105 else
106 -- we got an ANSI sequence
107 local seq = { key:byte(1, #key) }
108 print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")")
109 end
110end
111
112
113
114-- Clean up afterwards
115sys.setnonblock(io.stdin, false)
116sys.setconsoleflags(io.stdout, of_out)
117sys.setconsoleflags(io.stdin, of_in)
118sys.tcsetattr(io.stdin, sys.TCSANOW, of_attr)
119sys.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 @@
1local sys = require("system")
2
3print [[
4
5An example to display a spinner, whilst a long running task executes.
6
7]]
8
9
10-- start make backup, to auto-restore on exit
11sys.autotermrestore()
12-- configure console
13sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT)
14local of = sys.tcgetattr(io.stdin)
15sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = of.lflag - sys.L_ICANON - sys.L_ECHO })
16sys.setnonblock(io.stdin, true)
17
18
19
20local function hideCursor()
21 io.write("\27[?25l")
22 io.flush()
23end
24
25local function showCursor()
26 io.write("\27[?25h")
27 io.flush()
28end
29
30local function left(n)
31 io.write("\27[",n or 1,"D")
32 io.flush()
33end
34
35
36
37local spinner do
38 local spin = [[|/-\]]
39 local i = 1
40 spinner = function()
41 hideCursor()
42 io.write(spin:sub(i, i))
43 left()
44 i = i + 1
45 if i > #spin then i = 1 end
46
47 if sys.keypressed() then
48 sys.readkey() -- consume key pressed
49 io.write(" ");
50 left()
51 showCursor()
52 return true
53 else
54 return false
55 end
56 end
57end
58
59io.stdout:write("press any key to stop the spinner... ")
60while not spinner() do
61 sys.sleep(0.1)
62end
63
64print("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 @@
1local sys = require "system"
2
3print [[
4
5This example will draw a snake like spiral on the screen. Showing ANSI escape
6codes for moving the cursor around.
7
8]]
9
10-- backup term settings with auto-restore on exit
11sys.autotermrestore()
12
13-- setup Windows console to handle ANSI processing
14sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
15
16-- start drawing the spiral.
17-- start from current pos, then right, then up, then left, then down, and again.
18local x, y = 1, 1 -- current position
19local dx, dy = 1, 0 -- direction after each step
20local wx, wy = 30, 30 -- width and height of the room
21local mx, my = 1, 1 -- margin
22
23-- commands to move the cursor
24local move_left = "\27[1D"
25local move_right = "\27[1C"
26local move_up = "\27[1A"
27local move_down = "\27[1B"
28
29-- create room: 30 empty lines
30print(("\n"):rep(wy))
31local move = move_right
32
33while wx > 0 and wy > 0 do
34 sys.sleep(0.01) -- slow down the drawing a little
35 io.write("*" .. move_left .. move )
36 io.flush()
37 x = x + dx
38 y = y + dy
39
40 if x > wx and move == move_right then
41 -- end of move right
42 dx = 0
43 dy = 1
44 move = move_up
45 wy = wy - 1
46 my = my + 1
47 elseif y > wy and move == move_up then
48 -- end of move up
49 dx = -1
50 dy = 0
51 move = move_left
52 wx = wx - 1
53 mx = mx + 1
54 elseif x < mx and move == move_left then
55 -- end of move left
56 dx = 0
57 dy = -1
58 move = move_down
59 wy = wy - 1
60 my = my + 1
61 elseif y < my and move == move_down then
62 -- end of move down
63 dx = 1
64 dy = 0
65 move = move_right
66 wx = wx - 1
67 mx = mx + 1
68 end
69end
70
71io.write(move_down:rep(15))
72print("\nDone!")
diff --git a/luasystem-scm-0.rockspec b/luasystem-scm-0.rockspec
index ff5af61..dac3d9b 100644
--- a/luasystem-scm-0.rockspec
+++ b/luasystem-scm-0.rockspec
@@ -59,6 +59,7 @@ local function make_platform(plat)
59 'src/environment.c', 59 'src/environment.c',
60 'src/random.c', 60 'src/random.c',
61 'src/term.c', 61 'src/term.c',
62 'src/bitflags.c',
62 }, 63 },
63 defines = defines[plat], 64 defines = defines[plat],
64 libraries = libraries[plat], 65 libraries = libraries[plat],
diff --git a/spec/04-term_spec.lua b/spec/04-term_spec.lua
index a2034aa..9ca37e9 100644
--- a/spec/04-term_spec.lua
+++ b/spec/04-term_spec.lua
@@ -91,4 +91,24 @@ describe("Terminal:", function()
91 91
92 end) 92 end)
93 93
94
95
96 describe("getconsoleflags()", function()
97
98 pending("returns the consoleflags, if called without flags", function()
99print"1"
100package.loaded["system"] = nil
101package.loaded["system.core"] = nil
102print"2"
103local system = require "system"
104print"3"
105for k,v in pairs(system) do print(k,v) end
106for k,v in pairs(debug.getinfo(system.isatty)) do print(k,v) end
107
108 local flags, err = system.getconsoleflags(io.stdin)
109 assert.is_nil(err)
110 assert.is_integer(flags)
111 end)
112
113 end)
94end) 114end)
diff --git a/spec/05-bitflags_spec.lua b/spec/05-bitflags_spec.lua
new file mode 100644
index 0000000..01bf958
--- /dev/null
+++ b/spec/05-bitflags_spec.lua
@@ -0,0 +1,108 @@
1describe("BitFlags library", function()
2
3 local sys = require("system")
4
5 it("creates new flag objects", function()
6 local bf = sys.bitflag(255)
7 assert.is_not_nil(bf)
8 assert.are.equal(255, bf:value())
9 assert.is.userdata(bf)
10 end)
11
12 it("converts to a hex string", function()
13 local bf = sys.bitflag(255)
14 assert.are.equal("bitflags: 255", tostring(bf))
15 end)
16
17 it("handles OR/ADD operations", function()
18 -- one at a time
19 local bf1 = sys.bitflag(1) -- b0001
20 local bf2 = sys.bitflag(2) -- b0010
21 local bf3 = bf1 + bf2 -- b0011
22 assert.are.equal(3, bf3:value())
23 -- multiple at once
24 local bf4 = sys.bitflag(4+8) -- b1100
25 local bf5 = bf3 + bf4 -- b1111
26 assert.are.equal(15, bf5:value())
27 -- multiple that were already set
28 local bf6 = sys.bitflag(15) -- b1111
29 local bf7 = sys.bitflag(8+2) -- b1010
30 local bf8 = bf6 + bf7 -- b1111
31 assert.are.equal(15, bf8:value())
32 end)
33
34 it("handles AND-NOT/SUBSTRACT operations", function()
35 -- one at a time
36 local bf1 = sys.bitflag(3) -- b0011
37 local bf2 = sys.bitflag(1) -- b0001
38 local bf3 = bf1 - bf2 -- b0010
39 assert.are.equal(2, bf3:value())
40 -- multiple at once
41 local bf4 = sys.bitflag(15) -- b1111
42 local bf5 = sys.bitflag(8+2) -- b1010
43 local bf6 = bf4 - bf5 -- b0101
44 assert.are.equal(5, bf6:value())
45 -- multiple that were not set
46 local bf7 = sys.bitflag(3) -- b0011
47 local bf8 = sys.bitflag(15) -- b1111
48 local bf9 = bf7 - bf8 -- b0000
49 assert.are.equal(0, bf9:value())
50 end)
51
52 it("checks for equality", function()
53 local bf1 = sys.bitflag(4)
54 local bf2 = sys.bitflag(4)
55 local bf3 = sys.bitflag(5)
56 assert.is.True(bf1 == bf2)
57 assert.is.False(bf1 == bf3)
58 end)
59
60 it("indexes bits correctly", function()
61 local bf = sys.bitflag(4) -- b100
62 assert.is_true(bf[2])
63 assert.is_false(bf[1])
64 end)
65
66 it("errors on reading invalid bit indexes", function()
67 local bf = sys.bitflag(4)
68 assert.has_error(function() return bf[-10] end, "index out of range")
69 assert.has_error(function() return bf[10000] end, "index out of range")
70 assert.has_no_error(function() return bf.not_a_number end)
71 end)
72
73 it("sets and clears bits correctly", function()
74 local bf = sys.bitflag(0)
75 bf[1] = true
76 assert.is_true(bf[1])
77 bf[1] = false
78 assert.is_false(bf[1])
79 end)
80
81 it("errors on setting invalid bit indexes", function()
82 local bf = sys.bitflag(0)
83 assert.has_error(function() bf[-10] = true end, "index out of range")
84 assert.has_error(function() bf[10000] = true end, "index out of range")
85 assert.has_error(function() bf.not_a_number = true end, "index must be a number")
86 end)
87
88 it("handles <= and >= operations", function()
89 local bf1 = sys.bitflag(3) -- b0011
90 local bf2 = sys.bitflag(15) -- b1111
91 assert.is_true(bf2 >= bf1) -- all bits in bf1 are set in bf2
92 assert.is_true(bf2 > bf1) -- all bits in bf1 are set in bf2 and some more
93 assert.is_false(bf2 <= bf1) -- not all bits in bf2 are set in bf1
94 assert.is_false(bf2 < bf1) -- not all bits in bf2 are set in bf1
95 end)
96
97 it("checks for a subset using 'has'", function()
98 local bf1 = sys.bitflag(3) -- b0011
99 local bf2 = sys.bitflag(3) -- b0011
100 local bf3 = sys.bitflag(15) -- b1111
101 local bf0 = sys.bitflag(0) -- b0000
102 assert.is_true(bf1:has(bf2)) -- equal
103 assert.is_true(bf3:has(bf1)) -- is a subset, and has more flags
104 assert.is_false(bf1:has(bf3)) -- not a subset, bf3 has more flags
105 assert.is_false(bf1:has(bf0)) -- bf0 is unset, always returns false
106 end)
107
108end)
diff --git a/src/bitflags.c b/src/bitflags.c
new file mode 100644
index 0000000..89a88b7
--- /dev/null
+++ b/src/bitflags.c
@@ -0,0 +1,235 @@
1/// Bitflags module.
2// The bitflag object makes it easy to manipulate flags in a bitmask.
3//
4// It has metamethods that do the hard work, adding flags sets them, substracting
5// unsets them. Comparing flags checks if all flags in the second set are also set
6// in the first set. The `has` method checks if all flags in the second set are
7// also set in the first set, but behaves slightly different.
8//
9// Indexing allows checking values or setting them by bit index (eg. 0-7 for flags
10// in the first byte).
11//
12// _NOTE_: unavailable flags (eg. Windows flags on a Posix system) should not be
13// omitted, but be assigned a value of 0. This is because the `has` method will
14// return `false` if the flags are checked and the value is 0.
15//
16// See `system.bitflag` (the constructor) for extensive examples on usage.
17// @classmod bitflags
18#include "bitflags.h"
19
20#define BITFLAGS_MT_NAME "LuaSystem.BitFlags"
21
22typedef struct {
23 LSBF_BITFLAG flags;
24} LS_BitFlags;
25
26/// Bit flags.
27// Bitflag objects can be used to easily manipulate and compare bit flags.
28// These are primarily for use with the terminal functions, but can be used
29// in other places as well.
30// @section bitflags
31
32
33// pushes a new LS_BitFlags object with the given value onto the stack
34void lsbf_pushbitflags(lua_State *L, LSBF_BITFLAG value) {
35 LS_BitFlags *obj = (LS_BitFlags *)lua_newuserdata(L, sizeof(LS_BitFlags));
36 if (!obj) luaL_error(L, "Memory allocation failed");
37 luaL_getmetatable(L, BITFLAGS_MT_NAME);
38 lua_setmetatable(L, -2);
39 obj->flags = value;
40}
41
42// gets the LS_BitFlags value at the given index. Returns a Lua error if it is not
43// a LS_BitFlags object.
44LSBF_BITFLAG lsbf_checkbitflags(lua_State *L, int index) {
45 LS_BitFlags *obj = (LS_BitFlags *)luaL_checkudata(L, index, BITFLAGS_MT_NAME);
46 return obj->flags;
47}
48
49/***
50Creates a new bitflag object from the given value.
51@function system.bitflag
52@tparam[opt=0] number value the value to create the bitflag object from.
53@treturn bitflag bitflag object with the given values set.
54@usage
55local sys = require 'system'
56local flags = sys.bitflag(2) -- b0010
57
58-- get state of individual bits
59print(flags[0]) -- false
60print(flags[1]) -- true
61
62-- set individual bits
63flags[0] = true -- b0011
64print(flags:value()) -- 3
65print(flags) -- "bitflags: 3"
66
67-- adding flags (bitwise OR)
68local flags1 = sys.bitflag(1) -- b0001
69local flags2 = sys.bitflag(2) -- b0010
70local flags3 = flags1 + flags2 -- b0011
71
72-- substracting flags (bitwise AND NOT)
73print(flags3:value()) -- 3
74flag3 = flag3 - flag3 -- b0000
75print(flags3:value()) -- 0
76
77-- comparing flags
78local flags4 = sys.bitflag(7) -- b0111
79local flags5 = sys.bitflag(255) -- b11111111
80print(flags5 >= flags4) -- true, all bits in flags4 are set in flags5
81
82-- comparing with 0 flags: comparison and `has` behave differently
83local flags6 = sys.bitflag(0) -- b0000
84local flags7 = sys.bitflag(1) -- b0001
85print(flags6 < flags7) -- true, flags6 is a subset of flags7
86print(flags7:has(flags6)) -- false, flags6 is not set in flags7
87*/
88static int lsbf_new(lua_State *L) {
89 LSBF_BITFLAG flags = 0;
90 if (lua_gettop(L) > 0) {
91 flags = luaL_checkinteger(L, 1);
92 }
93 lsbf_pushbitflags(L, flags);
94 return 1;
95}
96
97/***
98Retrieves the numeric value of the bitflag object.
99@function bitflag:value
100@treturn number the numeric value of the bitflags.
101@usage
102local sys = require 'system'
103local flags = sys.bitflag() -- b0000
104flags[0] = true -- b0001
105flags[2] = true -- b0101
106print(flags:value()) -- 5
107*/
108static int lsbf_value(lua_State *L) {
109 lua_pushinteger(L, lsbf_checkbitflags(L, 1));
110 return 1;
111}
112
113static int lsbf_tostring(lua_State *L) {
114 lua_pushfstring(L, "bitflags: %d", lsbf_checkbitflags(L, 1));
115 return 1;
116}
117
118static int lsbf_add(lua_State *L) {
119 lsbf_pushbitflags(L, lsbf_checkbitflags(L, 1) | lsbf_checkbitflags(L, 2));
120 return 1;
121}
122
123static int lsbf_sub(lua_State *L) {
124 lsbf_pushbitflags(L, lsbf_checkbitflags(L, 1) & ~lsbf_checkbitflags(L, 2));
125 return 1;
126}
127
128static int lsbf_eq(lua_State *L) {
129 lua_pushboolean(L, lsbf_checkbitflags(L, 1) == lsbf_checkbitflags(L, 2));
130 return 1;
131}
132
133static int lsbf_le(lua_State *L) {
134 LSBF_BITFLAG a = lsbf_checkbitflags(L, 1);
135 LSBF_BITFLAG b = lsbf_checkbitflags(L, 2);
136 // Check if all bits in b are also set in a
137 lua_pushboolean(L, (a & b) == a);
138 return 1;
139}
140
141/***
142Checks if the given flags are set.
143This is different from the `>=` and `<=` operators because if the flag to check
144has a value `0`, it will always return `false`. So if there are flags that are
145unsupported on a platform, they can be set to 0 and the `has` function will
146return `false` if the flags are checked.
147@function bitflag:has
148@tparam bitflag subset the flags to check for.
149@treturn boolean true if all the flags are set, false otherwise.
150@usage
151local sys = require 'system'
152local flags = sys.bitflag(12) -- b1100
153local myflags = sys.bitflag(15) -- b1111
154print(flags:has(myflags)) -- false, not all bits in myflags are set in flags
155print(myflags:has(flags)) -- true, all bits in flags are set in myflags
156*/
157static int lsbf_has(lua_State *L) {
158 LSBF_BITFLAG a = lsbf_checkbitflags(L, 1);
159 LSBF_BITFLAG b = lsbf_checkbitflags(L, 2);
160 // Check if all bits in b are also set in a, and b is not 0
161 lua_pushboolean(L, (a | b) == a && b != 0);
162 return 1;
163}
164
165static int lsbf_lt(lua_State *L) {
166 LSBF_BITFLAG a = lsbf_checkbitflags(L, 1);
167 LSBF_BITFLAG b = lsbf_checkbitflags(L, 2);
168 // Check if a is strictly less than b, meaning a != b and a is a subset of b
169 lua_pushboolean(L, (a != b) && ((a & b) == a));
170 return 1;
171}
172
173static int lsbf_index(lua_State *L) {
174 if (!lua_isnumber(L, 2)) {
175 // the parameter isn't a number, just lookup the key in the metatable
176 lua_getmetatable(L, 1);
177 lua_pushvalue(L, 2);
178 lua_gettable(L, -2);
179 return 1;
180 }
181
182 int index = luaL_checkinteger(L, 2);
183 if (index < 0 || index >= sizeof(LSBF_BITFLAG) * 8) {
184 return luaL_error(L, "index out of range");
185 }
186 lua_pushboolean(L, (lsbf_checkbitflags(L, 1) & (1 << index)) != 0);
187 return 1;
188}
189
190static int lsbf_newindex(lua_State *L) {
191 LS_BitFlags *obj = (LS_BitFlags *)luaL_checkudata(L, 1, BITFLAGS_MT_NAME);
192
193 if (!lua_isnumber(L, 2)) {
194 return luaL_error(L, "index must be a number");
195 }
196 int index = luaL_checkinteger(L, 2);
197 if (index < 0 || index >= sizeof(LSBF_BITFLAG) * 8) {
198 return luaL_error(L, "index out of range");
199 }
200
201 luaL_checkany(L, 3);
202 if (lua_toboolean(L, 3)) {
203 obj->flags |= (1 << index);
204 } else {
205 obj->flags &= ~(1 << index);
206 }
207 return 0;
208}
209
210static const struct luaL_Reg lsbf_funcs[] = {
211 {"bitflag", lsbf_new},
212 {NULL, NULL}
213};
214
215static const struct luaL_Reg lsbf_methods[] = {
216 {"value", lsbf_value},
217 {"has", lsbf_has},
218 {"__tostring", lsbf_tostring},
219 {"__add", lsbf_add},
220 {"__sub", lsbf_sub},
221 {"__eq", lsbf_eq},
222 {"__le", lsbf_le},
223 {"__lt", lsbf_lt},
224 {"__index", lsbf_index},
225 {"__newindex", lsbf_newindex},
226 {NULL, NULL}
227};
228
229void bitflags_open(lua_State *L) {
230 luaL_newmetatable(L, BITFLAGS_MT_NAME);
231 luaL_setfuncs(L, lsbf_methods, 0);
232 lua_pop(L, 1);
233
234 luaL_setfuncs(L, lsbf_funcs, 0);
235}
diff --git a/src/bitflags.h b/src/bitflags.h
new file mode 100644
index 0000000..0e47246
--- /dev/null
+++ b/src/bitflags.h
@@ -0,0 +1,21 @@
1#ifndef LSBITFLAGS_H
2#define LSBITFLAGS_H
3
4#include <lua.h>
5#include "compat.h"
6#include <lauxlib.h>
7#include <stdlib.h>
8
9// type used to store the bitflags
10#define LSBF_BITFLAG lua_Integer
11
12// Validates that the given index is a bitflag object and returns its value.
13// If the index is not a bitflag object, a Lua error is raised.
14// The value will be left on the stack.
15LSBF_BITFLAG lsbf_checkbitflags(lua_State *L, int index);
16
17// Pushes a new bitflag object with the given value onto the stack.
18// Might raise a Lua error if memory allocation fails.
19void lsbf_pushbitflags(lua_State *L, LSBF_BITFLAG value);
20
21#endif
diff --git a/src/compat.h b/src/compat.h
index 35f9ef2..7a1fcee 100644
--- a/src/compat.h
+++ b/src/compat.h
@@ -13,6 +13,17 @@ void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup);
13#include <sys/types.h> 13#include <sys/types.h>
14#endif 14#endif
15 15
16// Windows compatibility; define DWORD and TRUE/FALSE on non-Windows
17#ifndef _WIN32
18#ifndef DWORD
19#define DWORD unsigned long
20#endif
21#ifndef TRUE
22#define TRUE 1
23#define FALSE 0
24#endif
25#endif
26
16#ifdef _MSC_VER 27#ifdef _MSC_VER
17// MSVC Windows doesn't have ssize_t, so we define it here 28// MSVC Windows doesn't have ssize_t, so we define it here
18#if SIZE_MAX == UINT_MAX 29#if SIZE_MAX == UINT_MAX
diff --git a/src/core.c b/src/core.c
index 729023f..d233ecc 100644
--- a/src/core.c
+++ b/src/core.c
@@ -16,6 +16,7 @@ void time_open(lua_State *L);
16void environment_open(lua_State *L); 16void environment_open(lua_State *L);
17void random_open(lua_State *L); 17void random_open(lua_State *L);
18void term_open(lua_State *L); 18void term_open(lua_State *L);
19void bitflags_open(lua_State *L);
19 20
20/*------------------------------------------------------------------------- 21/*-------------------------------------------------------------------------
21 * Initializes all library modules. 22 * Initializes all library modules.
@@ -32,6 +33,7 @@ LUAEXPORT int luaopen_system_core(lua_State *L) {
32 lua_pushboolean(L, 0); 33 lua_pushboolean(L, 0);
33#endif 34#endif
34 lua_rawset(L, -3); 35 lua_rawset(L, -3);
36 bitflags_open(L); // must be first, used by others
35 time_open(L); 37 time_open(L);
36 random_open(L); 38 random_open(L);
37 term_open(L); 39 term_open(L);
diff --git a/src/term.c b/src/term.c
index 2adb1e9..062394d 100644
--- a/src/term.c
+++ b/src/term.c
@@ -1,37 +1,849 @@
1/// @submodule system 1/// @submodule system
2
3// Unix: see https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios-termios3-and-stty/
4// Windows: see https://learn.microsoft.com/en-us/windows/console/console-reference
5
2#include <lua.h> 6#include <lua.h>
3#include <lauxlib.h> 7#include <lauxlib.h>
4#include <lualib.h> 8#include <lualib.h>
5#include "compat.h" 9#include "compat.h"
10#include "bitflags.h"
6 11
7#ifndef _MSC_VER 12#ifndef _MSC_VER
8# include <unistd.h> 13# include <unistd.h>
9#endif 14#endif
10 15
16#ifdef _WIN32
17# include <windows.h>
18#else
19# include <termios.h>
20# include <string.h>
21# include <errno.h>
22# include <fcntl.h>
23# include <sys/ioctl.h>
24# include <unistd.h>
25#endif
26
27#ifdef _WIN32
28// after an error is returned, GetLastError() result can be passed to this function to get a string
29// representation of the error on the stack.
30// result will be nil+error on the stack, always 2 results.
31static void termFormatError(lua_State *L, DWORD errorCode, const char* prefix) {
32//static void FormatErrorAndReturn(lua_State *L, DWORD errorCode, const char* prefix) {
33 LPSTR messageBuffer = NULL;
34 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
35 NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
36
37 lua_pushnil(L);
38 if (messageBuffer) {
39 if (prefix) {
40 lua_pushfstring(L, "%s: %s", prefix, messageBuffer);
41 } else {
42 lua_pushstring(L, messageBuffer);
43 }
44 LocalFree(messageBuffer);
45 } else {
46 lua_pushfstring(L, "%sError code %d", prefix ? prefix : "", errorCode);
47 }
48}
49#else
50static int pusherror(lua_State *L, const char *info)
51{
52 lua_pushnil(L);
53 if (info==NULL)
54 lua_pushstring(L, strerror(errno));
55 else
56 lua_pushfstring(L, "%s: %s", info, strerror(errno));
57 lua_pushinteger(L, errno);
58 return 3;
59}
60#endif
11 61
12/*** 62/***
13Checks if a file-handle is a TTY. 63Checks if a file-handle is a TTY.
14 64
15@function isatty 65@function isatty
16@tparam file file the file-handle to check 66@tparam file file the file-handle to check, one of `io.stdin`, `io.stdout`, `io.stderr`.
17@treturn boolean true if the file is a tty 67@treturn boolean true if the file is a tty
68@usage
69local system = require('system')
70if system.isatty(io.stdin) then
71 -- enable ANSI coloring etc on Windows, does nothing in Posix.
72 local flags = system.getconsoleflags(io.stdout)
73 system.setconsoleflags(io.stdout, flags + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
74end
18*/ 75*/
19static int lua_isatty(lua_State* L) { 76static int lst_isatty(lua_State* L) {
20 FILE **fh = (FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE); 77 FILE **fh = (FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE);
21 lua_pushboolean(L, isatty(fileno(*fh))); 78 lua_pushboolean(L, isatty(fileno(*fh)));
22 return 1; 79 return 1;
23} 80}
24 81
25 82
83/*-------------------------------------------------------------------------
84 * Windows Get/SetConsoleMode functions
85 *-------------------------------------------------------------------------*/
26 86
27static luaL_Reg func[] = { 87typedef struct ls_RegConst {
28 { "isatty", lua_isatty }, 88 const char *name;
29 { NULL, NULL } 89 DWORD value;
90} ls_RegConst;
91
92// Define a macro to check if a constant is defined and set it to 0 if not.
93// This is needed because some flags are not defined on all platforms. So we
94// still export the constants, but they will be all 0, and hence not do anything.
95#ifdef _WIN32
96#define CHECK_WIN_FLAG_OR_ZERO(flag) flag
97#define CHECK_NIX_FLAG_OR_ZERO(flag) 0
98#else
99#define CHECK_WIN_FLAG_OR_ZERO(flag) 0
100#define CHECK_NIX_FLAG_OR_ZERO(flag) flag
101#endif
102
103// Export Windows constants to Lua
104static const struct ls_RegConst win_console_in_flags[] = {
105 // Console Input Flags
106 {"CIF_ECHO_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_ECHO_INPUT)},
107 {"CIF_INSERT_MODE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_INSERT_MODE)},
108 {"CIF_LINE_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_LINE_INPUT)},
109 {"CIF_MOUSE_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_MOUSE_INPUT)},
110 {"CIF_PROCESSED_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_PROCESSED_INPUT)},
111 {"CIF_QUICK_EDIT_MODE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_QUICK_EDIT_MODE)},
112 {"CIF_WINDOW_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_WINDOW_INPUT)},
113 {"CIF_VIRTUAL_TERMINAL_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_VIRTUAL_TERMINAL_INPUT)},
114 {"CIF_EXTENDED_FLAGS", CHECK_WIN_FLAG_OR_ZERO(ENABLE_EXTENDED_FLAGS)},
115 {"CIF_AUTO_POSITION", CHECK_WIN_FLAG_OR_ZERO(ENABLE_AUTO_POSITION)},
116 {NULL, 0}
117};
118
119static const struct ls_RegConst win_console_out_flags[] = {
120 // Console Output Flags
121 {"COF_PROCESSED_OUTPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_PROCESSED_OUTPUT)},
122 {"COF_WRAP_AT_EOL_OUTPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_WRAP_AT_EOL_OUTPUT)},
123 {"COF_VIRTUAL_TERMINAL_PROCESSING", CHECK_WIN_FLAG_OR_ZERO(ENABLE_VIRTUAL_TERMINAL_PROCESSING)},
124 {"COF_DISABLE_NEWLINE_AUTO_RETURN", CHECK_WIN_FLAG_OR_ZERO(DISABLE_NEWLINE_AUTO_RETURN)},
125 {"COF_ENABLE_LVB_GRID_WORLDWIDE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_LVB_GRID_WORLDWIDE)},
126 {NULL, 0}
127};
128
129
130// Export Unix constants to Lua
131static const struct ls_RegConst nix_tcsetattr_actions[] = {
132 // The optional actions for tcsetattr
133 {"TCSANOW", CHECK_NIX_FLAG_OR_ZERO(TCSANOW)},
134 {"TCSADRAIN", CHECK_NIX_FLAG_OR_ZERO(TCSADRAIN)},
135 {"TCSAFLUSH", CHECK_NIX_FLAG_OR_ZERO(TCSAFLUSH)},
136 {NULL, 0}
137};
138
139static const struct ls_RegConst nix_console_i_flags[] = {
140 // Input flags (c_iflag)
141 {"I_IGNBRK", CHECK_NIX_FLAG_OR_ZERO(IGNBRK)},
142 {"I_BRKINT", CHECK_NIX_FLAG_OR_ZERO(BRKINT)},
143 {"I_IGNPAR", CHECK_NIX_FLAG_OR_ZERO(IGNPAR)},
144 {"I_PARMRK", CHECK_NIX_FLAG_OR_ZERO(PARMRK)},
145 {"I_INPCK", CHECK_NIX_FLAG_OR_ZERO(INPCK)},
146 {"I_ISTRIP", CHECK_NIX_FLAG_OR_ZERO(ISTRIP)},
147 {"I_INLCR", CHECK_NIX_FLAG_OR_ZERO(INLCR)},
148 {"I_IGNCR", CHECK_NIX_FLAG_OR_ZERO(IGNCR)},
149 {"I_ICRNL", CHECK_NIX_FLAG_OR_ZERO(ICRNL)},
150#ifndef __APPLE__
151 {"I_IUCLC", CHECK_NIX_FLAG_OR_ZERO(IUCLC)}, // Might not be available on all systems
152#else
153 {"I_IUCLC", 0},
154#endif
155 {"I_IXON", CHECK_NIX_FLAG_OR_ZERO(IXON)},
156 {"I_IXANY", CHECK_NIX_FLAG_OR_ZERO(IXANY)},
157 {"I_IXOFF", CHECK_NIX_FLAG_OR_ZERO(IXOFF)},
158 {"I_IMAXBEL", CHECK_NIX_FLAG_OR_ZERO(IMAXBEL)},
159 {NULL, 0}
30}; 160};
31 161
162static const struct ls_RegConst nix_console_o_flags[] = {
163 // Output flags (c_oflag)
164 {"O_OPOST", CHECK_NIX_FLAG_OR_ZERO(OPOST)},
165#ifndef __APPLE__
166 {"O_OLCUC", CHECK_NIX_FLAG_OR_ZERO(OLCUC)}, // Might not be available on all systems
167#else
168 {"O_OLCUC", 0},
169#endif
170 {"O_ONLCR", CHECK_NIX_FLAG_OR_ZERO(ONLCR)},
171 {"O_OCRNL", CHECK_NIX_FLAG_OR_ZERO(OCRNL)},
172 {"O_ONOCR", CHECK_NIX_FLAG_OR_ZERO(ONOCR)},
173 {"O_ONLRET", CHECK_NIX_FLAG_OR_ZERO(ONLRET)},
174 {"O_OFILL", CHECK_NIX_FLAG_OR_ZERO(OFILL)},
175 {"O_OFDEL", CHECK_NIX_FLAG_OR_ZERO(OFDEL)},
176 {"O_NLDLY", CHECK_NIX_FLAG_OR_ZERO(NLDLY)},
177 {"O_CRDLY", CHECK_NIX_FLAG_OR_ZERO(CRDLY)},
178 {"O_TABDLY", CHECK_NIX_FLAG_OR_ZERO(TABDLY)},
179 {"O_BSDLY", CHECK_NIX_FLAG_OR_ZERO(BSDLY)},
180 {"O_VTDLY", CHECK_NIX_FLAG_OR_ZERO(VTDLY)},
181 {"O_FFDLY", CHECK_NIX_FLAG_OR_ZERO(FFDLY)},
182 {NULL, 0}
183};
184
185static const struct ls_RegConst nix_console_l_flags[] = {
186 // Local flags (c_lflag)
187 {"L_ISIG", CHECK_NIX_FLAG_OR_ZERO(ISIG)},
188 {"L_ICANON", CHECK_NIX_FLAG_OR_ZERO(ICANON)},
189#ifndef __APPLE__
190 {"L_XCASE", CHECK_NIX_FLAG_OR_ZERO(XCASE)}, // Might not be available on all systems
191#else
192 {"L_XCASE", 0},
193#endif
194 {"L_ECHO", CHECK_NIX_FLAG_OR_ZERO(ECHO)},
195 {"L_ECHOE", CHECK_NIX_FLAG_OR_ZERO(ECHOE)},
196 {"L_ECHOK", CHECK_NIX_FLAG_OR_ZERO(ECHOK)},
197 {"L_ECHONL", CHECK_NIX_FLAG_OR_ZERO(ECHONL)},
198 {"L_NOFLSH", CHECK_NIX_FLAG_OR_ZERO(NOFLSH)},
199 {"L_TOSTOP", CHECK_NIX_FLAG_OR_ZERO(TOSTOP)},
200 {"L_ECHOCTL", CHECK_NIX_FLAG_OR_ZERO(ECHOCTL)}, // Might not be available on all systems
201 {"L_ECHOPRT", CHECK_NIX_FLAG_OR_ZERO(ECHOPRT)}, // Might not be available on all systems
202 {"L_ECHOKE", CHECK_NIX_FLAG_OR_ZERO(ECHOKE)}, // Might not be available on all systems
203 {"L_FLUSHO", CHECK_NIX_FLAG_OR_ZERO(FLUSHO)},
204 {"L_PENDIN", CHECK_NIX_FLAG_OR_ZERO(PENDIN)},
205 {"L_IEXTEN", CHECK_NIX_FLAG_OR_ZERO(IEXTEN)},
206 {NULL, 0}
207};
208
209static DWORD win_valid_in_flags = 0;
210static DWORD win_valid_out_flags = 0;
211static DWORD nix_valid_i_flags = 0;
212static DWORD nix_valid_o_flags = 0;
213static DWORD nix_valid_l_flags = 0;
214static void initialize_valid_flags()
215{
216 win_valid_in_flags = 0;
217 for (int i = 0; win_console_in_flags[i].name != NULL; i++)
218 {
219 win_valid_in_flags |= win_console_in_flags[i].value;
220 }
221 win_valid_out_flags = 0;
222 for (int i = 0; win_console_out_flags[i].name != NULL; i++)
223 {
224 win_valid_out_flags |= win_console_out_flags[i].value;
225 }
226 nix_valid_i_flags = 0;
227 for (int i = 0; nix_console_i_flags[i].name != NULL; i++)
228 {
229 nix_valid_i_flags |= nix_console_i_flags[i].value;
230 }
231 nix_valid_o_flags = 0;
232 for (int i = 0; nix_console_o_flags[i].name != NULL; i++)
233 {
234 nix_valid_o_flags |= nix_console_o_flags[i].value;
235 }
236 nix_valid_l_flags = 0;
237 for (int i = 0; nix_console_l_flags[i].name != NULL; i++)
238 {
239 nix_valid_l_flags |= nix_console_l_flags[i].value;
240 }
241}
242
243#ifdef _WIN32
244// first item on the stack should be io.stdin, io.stderr, or io.stdout, second item
245// should be the flags to validate.
246// If it returns NULL, then it leaves nil+err on the stack
247static HANDLE get_console_handle(lua_State *L, int flags_optional)
248{
249 if (lua_gettop(L) < 1) {
250 luaL_argerror(L, 1, "expected file handle");
251 }
252
253 HANDLE handle;
254 DWORD valid;
255 FILE *file = *(FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE);
256 if (file == stdin && file != NULL) {
257 handle = GetStdHandle(STD_INPUT_HANDLE);
258 valid = win_valid_in_flags;
259
260 } else if (file == stdout && file != NULL) {
261 handle = GetStdHandle(STD_OUTPUT_HANDLE);
262 valid = win_valid_out_flags;
263
264 } else if (file == stderr && file != NULL) {
265 handle = GetStdHandle(STD_ERROR_HANDLE);
266 valid = win_valid_out_flags;
267
268 } else {
269 luaL_argerror(L, 1, "invalid file handle"); // does not return
270 }
271
272 if (handle == INVALID_HANDLE_VALUE) {
273 termFormatError(L, GetLastError(), "failed to retrieve std handle");
274 lua_error(L); // does not return
275 }
276
277 if (handle == NULL) {
278 lua_pushnil(L);
279 lua_pushliteral(L, "failed to get console handle");
280 return NULL;
281 }
282
283 if (flags_optional && lua_gettop(L) < 2) {
284 return handle;
285 }
286
287 if (lua_gettop(L) < 2) {
288 luaL_argerror(L, 2, "expected flags");
289 }
290
291 LSBF_BITFLAG flags = lsbf_checkbitflags(L, 2);
292 if ((flags & ~valid) != 0) {
293 luaL_argerror(L, 2, "invalid flags");
294 }
295
296 return handle;
297}
298#else
299// first item on the stack should be io.stdin, io.stderr, or io.stdout. Throws a
300// Lua error if the file is not one of these.
301static int get_console_handle(lua_State *L)
302{
303 FILE **file = (FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE);
304 if (file == NULL || *file == NULL) {
305 return luaL_argerror(L, 1, "expected file handle"); // call doesn't return
306 }
307
308 // Check if the file is stdin, stdout, or stderr
309 if (*file == stdin || *file == stdout || *file == stderr) {
310 // Push the file descriptor onto the Lua stack
311 return fileno(*file);
312 }
313
314 return luaL_argerror(L, 1, "invalid file handle"); // does not return
315}
316#endif
317
318
319
320/***
321Sets the console flags (Windows).
322The `CIF_` and `COF_` constants are available on the module table. Where `CIF` are the
323input flags (for use with `io.stdin`) and `COF` are the output flags (for use with
324`io.stdout`/`io.stderr`).
325
326To see flag status and constant names check `listconsoleflags`.
327
328Note: not all combinations of flags are allowed, as some are mutually exclusive or mutually required.
329See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode)
330@function setconsoleflags
331@tparam file file the file-handle to set the flags on
332@tparam bitflags bitflags the flags to set/unset
333@treturn[1] boolean `true` on success
334@treturn[2] nil
335@treturn[2] string error message
336@usage
337local system = require('system')
338system.listconsoleflags(io.stdout) -- List all the available flags and their current status
339
340local flags = system.getconsoleflags(io.stdout)
341assert(system.setconsoleflags(io.stdout,
342 flags + system.COF_VIRTUAL_TERMINAL_PROCESSING)
343
344system.listconsoleflags(io.stdout) -- List again to check the differences
345*/
346static int lst_setconsoleflags(lua_State *L)
347{
348#ifdef _WIN32
349 HANDLE console_handle = get_console_handle(L, 0);
350 if (console_handle == NULL) {
351 return 2; // error message is already on the stack
352 }
353 LSBF_BITFLAG new_console_mode = lsbf_checkbitflags(L, 2);
354
355 DWORD prev_console_mode;
356 if (GetConsoleMode(console_handle, &prev_console_mode) == 0)
357 {
358 termFormatError(L, GetLastError(), "failed to get console mode");
359 return 2;
360 }
361
362 int success = SetConsoleMode(console_handle, new_console_mode) != 0;
363 if (!success)
364 {
365 termFormatError(L, GetLastError(), "failed to set console mode");
366 return 2;
367 }
368
369#endif
370 lua_pushboolean(L, 1);
371 return 1;
372}
373
374
375
376/***
377Gets console flags (Windows).
378@function getconsoleflags
379@tparam file file the file-handle to get the flags from.
380@treturn[1] bitflags the current console flags.
381@treturn[2] nil
382@treturn[2] string error message
383@usage
384local system = require('system')
385
386local flags = system.getconsoleflags(io.stdout)
387print("Current stdout flags:", tostring(flags))
388
389if flags:has(system.COF_VIRTUAL_TERMINAL_PROCESSING + system.COF_PROCESSED_OUTPUT) then
390 print("Both flags are set")
391else
392 print("At least one flag is not set")
393end
394*/
395static int lst_getconsoleflags(lua_State *L)
396{
397 DWORD console_mode = 0;
398
399#ifdef _WIN32
400 HANDLE console_handle = get_console_handle(L, 1);
401 if (console_handle == NULL) {
402 return 2; // error message is already on the stack
403 }
404
405 if (GetConsoleMode(console_handle, &console_mode) == 0)
406 {
407 lua_pushnil(L);
408 lua_pushliteral(L, "failed to get console mode");
409 return 2;
410 }
411
412#endif
413 lsbf_pushbitflags(L, console_mode);
414 return 1;
415}
416
417
418
419/*-------------------------------------------------------------------------
420 * Unix tcgetattr/tcsetattr functions
421 *-------------------------------------------------------------------------*/
422// Code modified from the LuaPosix library by Gary V. Vaughan
423// see https://github.com/luaposix/luaposix
424
425/***
426Get termios state.
427The terminal attributes is a table with the following fields:
428
429- `iflag` input flags
430- `oflag` output flags
431- `cflag` control flags
432- `lflag` local flags
433- `ispeed` input speed
434- `ospeed` output speed
435- `cc` control characters
436
437@function tcgetattr
438@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
439@treturn[1] termios terminal attributes, if successful. On Windows the bitflags are all 0, and the `cc` table is empty.
440@treturn[2] nil
441@treturn[2] string error message
442@treturn[2] int errnum
443@return error message if failed
444@usage
445local system = require('system')
446
447local status = assert(tcgetattr(io.stdin))
448if status.iflag:has(system.I_IGNBRK) then
449 print("Ignoring break condition")
450end
451*/
452static int lst_tcgetattr(lua_State *L)
453{
454#ifndef _WIN32
455 int r, i;
456 struct termios t;
457 int fd = get_console_handle(L);
458
459 r = tcgetattr(fd, &t);
460 if (r == -1) return pusherror(L, NULL);
461
462 lua_newtable(L);
463 lsbf_pushbitflags(L, t.c_iflag);
464 lua_setfield(L, -2, "iflag");
465
466 lsbf_pushbitflags(L, t.c_oflag);
467 lua_setfield(L, -2, "oflag");
468
469 lsbf_pushbitflags(L, t.c_lflag);
470 lua_setfield(L, -2, "lflag");
471
472 lsbf_pushbitflags(L, t.c_cflag);
473 lua_setfield(L, -2, "cflag");
474
475 lua_pushinteger(L, cfgetispeed(&t));
476 lua_setfield(L, -2, "ispeed");
477
478 lua_pushinteger(L, cfgetospeed(&t));
479 lua_setfield(L, -2, "ospeed");
480
481 lua_newtable(L);
482 for (i=0; i<NCCS; i++)
483 {
484 lua_pushinteger(L, i);
485 lua_pushinteger(L, t.c_cc[i]);
486 lua_settable(L, -3);
487 }
488 lua_setfield(L, -2, "cc");
489
490#else
491 lua_newtable(L);
492 lsbf_pushbitflags(L, 0);
493 lua_setfield(L, -2, "iflag");
494 lsbf_pushbitflags(L, 0);
495 lua_setfield(L, -2, "oflag");
496 lsbf_pushbitflags(L, 0);
497 lua_setfield(L, -2, "lflag");
498 lsbf_pushbitflags(L, 0);
499 lua_setfield(L, -2, "cflag");
500 lua_pushinteger(L, 0);
501 lua_setfield(L, -2, "ispeed");
502 lua_pushinteger(L, 0);
503 lua_setfield(L, -2, "ospeed");
504 lua_newtable(L);
505 lua_setfield(L, -2, "cc");
506
507#endif
508 return 1;
509}
510
511
512
513/***
514Set termios state.
515This function will set the flags as given.
516
517The `I_`, `O_`, and `L_` constants are available on the module table. They are the respective
518flags for the `iflags`, `oflags`, and `lflags` bitmasks.
519
520To see flag status and constant names check `listtermflags`. For their meaning check
521[the manpage](https://www.man7.org/linux/man-pages/man3/termios.3.html).
522
523_Note_: not all combinations of flags are allowed, as some are mutually exclusive or mutually required.
524See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode)
525
526_Note_: only `iflag`, `oflag`, and `lflag` are supported at the moment. The other fields are ignored.
527@function tcsetattr
528@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
529@int actions one of `TCSANOW`, `TCSADRAIN`, `TCSAFLUSH`
530@tparam table termios a table with bitflag fields:
531@tparam[opt] bitflags termios.iflag if given will set the input flags
532@tparam[opt] bitflags termios.oflag if given will set the output flags
533@tparam[opt] bitflags termios.lflag if given will set the local flags
534@treturn[1] bool `true`, if successful. Always returns `true` on Windows.
535@return[2] nil
536@treturn[2] string error message
537@treturn[2] int errnum
538@usage
539local system = require('system')
540
541local status = assert(tcgetattr(io.stdin))
542if not status.lflag:has(system.L_ECHO) then
543 -- if echo is off, turn echoing newlines on
544 tcsetattr(io.stdin, system.TCSANOW, { lflag = status.lflag + system.L_ECHONL }))
545end
546*/
547static int lst_tcsetattr(lua_State *L)
548{
549#ifndef _WIN32
550 struct termios t;
551 int r, i;
552 int fd = get_console_handle(L); // first is the console handle
553 int act = luaL_checkinteger(L, 2); // second is the action to take
554 luaL_checktype(L, 3, LUA_TTABLE); // third is the termios table with fields
555
556 r = tcgetattr(fd, &t);
557 if (r == -1) return pusherror(L, NULL);
558
559 lua_getfield(L, 3, "iflag");
560 if (!lua_isnil(L, -1)) {
561 t.c_iflag = lsbf_checkbitflags(L, -1);
562 }
563 lua_pop(L, 1);
564
565 lua_getfield(L, 3, "oflag");
566 if (!lua_isnil(L, -1)) {
567 t.c_oflag = lsbf_checkbitflags(L, -1);
568 }
569 lua_pop(L, 1);
570
571 lua_getfield(L, 3, "lflag");
572 if (!lua_isnil(L, -1)) {
573 t.c_lflag = lsbf_checkbitflags(L, -1);
574 }
575 lua_pop(L, 1);
576
577 // Skipping the others for now
578
579 // lua_getfield(L, 3, "cflag"); t.c_cflag = optint(L, -1, 0); lua_pop(L, 1);
580 // lua_getfield(L, 3, "ispeed"); cfsetispeed( &t, optint(L, -1, B0) ); lua_pop(L, 1);
581 // lua_getfield(L, 3, "ospeed"); cfsetospeed( &t, optint(L, -1, B0) ); lua_pop(L, 1);
582
583 // lua_getfield(L, 3, "cc");
584 // for (i=0; i<NCCS; i++)
585 // {
586 // lua_pushinteger(L, i);
587 // lua_gettable(L, -2);
588 // t.c_cc[i] = optint(L, -1, 0);
589 // lua_pop(L, 1);
590 // }
591
592 r = tcsetattr(fd, act, &t);
593 if (r == -1) return pusherror(L, NULL);
594#endif
595
596 lua_pushboolean(L, 1);
597 return 1;
598}
599
600
601
602/***
603Enables or disables non-blocking mode for a file (Posix).
604@function setnonblock
605@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
606@tparam boolean make_non_block a truthy value will enable non-blocking mode, a falsy value will disable it.
607@treturn[1] bool `true`, if successful
608@treturn[2] nil
609@treturn[2] string error message
610@treturn[2] int errnum
611@see getnonblock
612@usage
613local sys = require('system')
614
615-- set io.stdin to non-blocking mode
616local old_setting = sys.getnonblock(io.stdin)
617sys.setnonblock(io.stdin, true)
618
619-- do stuff
620
621-- restore old setting
622sys.setnonblock(io.stdin, old_setting)
623*/
624static int lst_setnonblock(lua_State *L)
625{
626#ifndef _WIN32
627
628 int fd = get_console_handle(L);
629
630 int flags = fcntl(fd, F_GETFL, 0);
631 if (flags == -1) {
632 return pusherror(L, "Error getting handle flags: ");
633 }
634 if (lua_toboolean(L, 2)) {
635 // truthy: set non-blocking
636 flags |= O_NONBLOCK;
637 } else {
638 // falsy: set disable non-blocking
639 flags &= ~O_NONBLOCK;
640 }
641 if (fcntl(fd, F_SETFL, flags) == -1) {
642 return pusherror(L, "Error changing O_NONBLOCK: ");
643 }
644
645#endif
646
647 lua_pushboolean(L, 1);
648 return 1;
649}
650
651
652
653/***
654Gets non-blocking mode status for a file (Posix).
655@function getnonblock
656@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
657@treturn[1] bool `true` if set to non-blocking, `false` if not. Always returns `false` on Windows.
658@treturn[2] nil
659@treturn[2] string error message
660@treturn[2] int errnum
661*/
662static int lst_getnonblock(lua_State *L)
663{
664#ifndef _WIN32
665
666 int fd = get_console_handle(L);
667
668 // Set O_NONBLOCK
669 int flags = fcntl(fd, F_GETFL, 0);
670 if (flags == -1) {
671 return pusherror(L, "Error getting handle flags: ");
672 }
673 if (flags & O_NONBLOCK) {
674 lua_pushboolean(L, 1);
675 } else {
676 lua_pushboolean(L, 0);
677 }
678
679#else
680 lua_pushboolean(L, 0);
681
682#endif
683 return 1;
684}
685
686
687
688/*-------------------------------------------------------------------------
689 * Reading keyboard input
690 *-------------------------------------------------------------------------*/
691
692/***
693Reads a key from the console non-blocking.
694On Posix, `io.stdin` must be set to non-blocking mode using `setnonblock`
695before calling this function. Otherwise it will block.
696
697@function readkey
698@treturn[1] integer the key code of the key that was pressed
699@treturn[2] nil if no key was pressed
700*/
701static int lst_readkey(lua_State *L) {
702#ifdef _WIN32
703 if (_kbhit()) {
704 lua_pushinteger(L, _getch());
705 return 1;
706 }
707 return 0;
708
709#else
710 char ch;
711 if (read(STDIN_FILENO, &ch, 1) > 0) {
712 lua_pushinteger(L, ch);
713 return 1;
714 }
715 return 0;
716
717#endif
718}
719
720/***
721Checks if a key has been pressed without reading it.
722On Posix, `io.stdin` must be set to non-blocking mode using `setnonblock`
723before calling this function. Otherwise it will block.
724
725@function keypressed
726@treturn boolean true if a key has been pressed, nil if not.
727*/
728static int lst_keypressed(lua_State *L) {
729#ifdef _WIN32
730 if (kbhit()) {
731 lua_pushboolean(L, 1);
732 return 1;
733 }
734 return 0;
735
736#else
737 char ch;
738 if (read(STDIN_FILENO, &ch, 1) > 0) {
739 // key was read, push back to stdin
740 ungetc(ch, stdin);
741 lua_pushboolean(L, 1);
742 return 1;
743 }
744 return 0;
745
746#endif
747}
748
749/*-------------------------------------------------------------------------
750 * Retrieve terminal size
751 *-------------------------------------------------------------------------*/
752
753
754/***
755Get the size of the terminal in columns and rows.
756@function termsize
757@treturn[1] int the number of columns
758@treturn[1] int the number of rows
759@treturn[2] nil
760@treturn[2] string error message
761*/
762static int lst_termsize(lua_State *L) {
763 int columns, rows;
764
765#ifdef _WIN32
766 CONSOLE_SCREEN_BUFFER_INFO csbi;
767 if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
768 termFormatError(L, GetLastError(), "Failed to get terminal size.");
769 return 2;
770 }
771 columns = csbi.srWindow.Right - csbi.srWindow.Left + 1;
772 rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
773
774#else
775 struct winsize ws;
776 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
777 return pusherror(L, "Failed to get terminal size.");
778 }
779 columns = ws.ws_col;
780 rows = ws.ws_row;
781
782#endif
783 lua_pushinteger(L, columns);
784 lua_pushinteger(L, rows);
785 return 2;
786}
787
788
789
32/*------------------------------------------------------------------------- 790/*-------------------------------------------------------------------------
33 * Initializes module 791 * Initializes module
34 *-------------------------------------------------------------------------*/ 792 *-------------------------------------------------------------------------*/
793
794static luaL_Reg func[] = {
795 { "isatty", lst_isatty },
796 { "getconsoleflags", lst_getconsoleflags },
797 { "setconsoleflags", lst_setconsoleflags },
798 { "tcgetattr", lst_tcgetattr },
799 { "tcsetattr", lst_tcsetattr },
800 { "getnonblock", lst_setnonblock },
801 { "setnonblock", lst_setnonblock },
802 { "readkey", lst_readkey },
803 { "keypressed", lst_keypressed },
804 { "termsize", lst_termsize },
805 { NULL, NULL }
806};
807
808
809
35void term_open(lua_State *L) { 810void term_open(lua_State *L) {
811 // set up constants and export the constants in module table
812 initialize_valid_flags();
813 // Windows flags
814 for (int i = 0; win_console_in_flags[i].name != NULL; i++)
815 {
816 lsbf_pushbitflags(L, win_console_in_flags[i].value);
817 lua_setfield(L, -2, win_console_in_flags[i].name);
818 }
819 for (int i = 0; win_console_out_flags[i].name != NULL; i++)
820 {
821 lsbf_pushbitflags(L, win_console_out_flags[i].value);
822 lua_setfield(L, -2, win_console_out_flags[i].name);
823 }
824 // Unix flags
825 for (int i = 0; nix_console_i_flags[i].name != NULL; i++)
826 {
827 lsbf_pushbitflags(L, nix_console_i_flags[i].value);
828 lua_setfield(L, -2, nix_console_i_flags[i].name);
829 }
830 for (int i = 0; nix_console_o_flags[i].name != NULL; i++)
831 {
832 lsbf_pushbitflags(L, nix_console_o_flags[i].value);
833 lua_setfield(L, -2, nix_console_o_flags[i].name);
834 }
835 for (int i = 0; nix_console_l_flags[i].name != NULL; i++)
836 {
837 lsbf_pushbitflags(L, nix_console_l_flags[i].value);
838 lua_setfield(L, -2, nix_console_l_flags[i].name);
839 }
840 // Unix tcsetattr actions
841 for (int i = 0; nix_tcsetattr_actions[i].name != NULL; i++)
842 {
843 lua_pushinteger(L, nix_tcsetattr_actions[i].value);
844 lua_setfield(L, -2, nix_tcsetattr_actions[i].name);
845 }
846
847 // export functions
36 luaL_setfuncs(L, func, 0); 848 luaL_setfuncs(L, func, 0);
37} 849}
diff --git a/system/init.lua b/system/init.lua
index 77e0c3b..c3ea94d 100644
--- a/system/init.lua
+++ b/system/init.lua
@@ -1,2 +1,210 @@
1local system = require 'system.core' 1--- Lua System Library.
2return system 2-- @module init
3
4local sys = require 'system.core'
5local global_backup -- global backup for terminal settings
6
7
8
9local add_gc_method do
10 -- feature detection; __GC meta-method, not available in all Lua versions
11 local has_gc = false
12 local tt = setmetatable({}, { -- luacheck: ignore
13 __gc = function() has_gc = true end
14 })
15
16 -- clear table and run GC to trigger
17 tt = nil
18 collectgarbage()
19 collectgarbage()
20
21
22 if has_gc then
23 -- use default GC mechanism since it is available
24 function add_gc_method(t, f)
25 setmetatable(t, { __gc = f })
26 end
27 else
28 -- create workaround using a proxy userdata, typical for Lua 5.1
29 function add_gc_method(t, f)
30 local proxy = newproxy(true)
31 getmetatable(proxy).__gc = function()
32 t["__gc_proxy"] = nil
33 f(t)
34 end
35 t["__gc_proxy"] = proxy
36 end
37 end
38end
39
40
41
42--- Returns a backup of terminal setting for stdin/out/err.
43-- Handles terminal/console flags and non-block flags on the streams.
44-- Backs up terminal/console flags only if a stream is a tty.
45-- @return table with backup of terminal settings
46function sys.termbackup()
47 local backup = {}
48
49 if sys.isatty(io.stdin) then
50 backup.console_in = sys.getconsoleflags(io.stdin)
51 backup.term_in = sys.tcgetattr(io.stdin)
52 end
53 if sys.isatty(io.stdout) then
54 backup.console_out = sys.getconsoleflags(io.stdout)
55 backup.term_out = sys.tcgetattr(io.stdout)
56 end
57 if sys.isatty(io.stderr) then
58 backup.console_err = sys.getconsoleflags(io.stderr)
59 backup.term_err = sys.tcgetattr(io.stderr)
60 end
61
62 backup.block_in = sys.getnonblock(io.stdin)
63 backup.block_out = sys.getnonblock(io.stdout)
64 backup.block_err = sys.getnonblock(io.stderr)
65
66 return backup
67end
68
69
70
71--- Restores terminal settings from a backup
72-- @tparam table backup the backup of terminal settings, see `termbackup`.
73-- @treturn boolean true
74function sys.termrestore(backup)
75 if backup.console_in then sys.setconsoleflags(io.stdin, backup.console_in) end
76 if backup.term_in then sys.tcsetattr(io.stdin, sys.TCSANOW, backup.term_in) end
77 if backup.console_out then sys.setconsoleflags(io.stdout, backup.console_out) end
78 if backup.term_out then sys.tcsetattr(io.stdout, sys.TCSANOW, backup.term_out) end
79 if backup.console_err then sys.setconsoleflags(io.stderr, backup.console_err) end
80 if backup.term_err then sys.tcsetattr(io.stderr, sys.TCSANOW, backup.term_err) end
81
82 if backup.block_in ~= nil then sys.setnonblock(io.stdin, backup.block_in) end
83 if backup.block_out ~= nil then sys.setnonblock(io.stdout, backup.block_out) end
84 if backup.block_err ~= nil then sys.setnonblock(io.stderr, backup.block_err) end
85 return true
86end
87
88
89
90--- Backs up terminal settings and restores them on application exit.
91-- Calls `termbackup` to back up terminal settings and sets up a GC method to
92-- automatically restore them on application exit (also works on Lua 5.1).
93-- @treturn[1] boolean true
94-- @treturn[2] nil if the backup was already created
95-- @treturn[2] string error message
96function sys.autotermrestore()
97 if global_backup then
98 return nil, "global terminal backup was already set up"
99 end
100 global_backup = sys.termbackup()
101 add_gc_method(global_backup, function(self)
102 sys.termrestore(self) end)
103 return true
104end
105
106
107
108do
109 local oldunpack = unpack or table.unpack
110 local pack = function(...) return { n = select("#", ...), ... } end
111 local unpack = function(t) return oldunpack(t, 1, t.n) end
112
113 --- Wraps a function to automatically restore terminal settings upon returning.
114 -- Calls `termbackup` before calling the function and `termrestore` after.
115 -- @tparam function f function to wrap
116 -- @treturn function wrapped function
117 function sys.termwrap(f)
118 if type(f) ~= "function" then
119 error("arg #1 to wrap, expected function, got " .. type(f), 2)
120 end
121
122 return function(...)
123 local bu = sys.termbackup()
124 local results = pack(f(...))
125 sys.termrestore(bu)
126 return unpack(results)
127 end
128 end
129end
130
131
132
133--- Debug function for console flags (Windows).
134-- Pretty prints the current flags set for the handle.
135-- @param fh file handle (`io.stdin`, `io.stdout`, `io.stderr`)
136-- @usage -- Print the flags for stdin/out/err
137-- system.listconsoleflags(io.stdin)
138-- system.listconsoleflags(io.stdout)
139-- system.listconsoleflags(io.stderr)
140function sys.listconsoleflags(fh)
141 local flagtype
142 if fh == io.stdin then
143 print "------ STDIN FLAGS WINDOWS ------"
144 flagtype = "CIF_"
145 elseif fh == io.stdout then
146 print "------ STDOUT FLAGS WINDOWS ------"
147 flagtype = "COF_"
148 elseif fh == io.stderr then
149 print "------ STDERR FLAGS WINDOWS ------"
150 flagtype = "COF_"
151 end
152
153 local flags = assert(sys.getconsoleflags(fh))
154 local out = {}
155 for k,v in pairs(sys) do
156 if type(k) == "string" and k:sub(1,4) == flagtype then
157 if flags:has(v) then
158 out[#out+1] = string.format("%10d [x] %s",v:value(),k)
159 else
160 out[#out+1] = string.format("%10d [ ] %s",v:value(),k)
161 end
162 end
163 end
164 table.sort(out)
165 for k,v in pairs(out) do
166 print(v)
167 end
168end
169
170
171
172--- Debug function for terminal flags (Posix).
173-- Pretty prints the current flags set for the handle.
174-- @param fh file handle (`io.stdin`, `io.stdout`, `io.stderr`)
175-- @usage -- Print the flags for stdin/out/err
176-- system.listconsoleflags(io.stdin)
177-- system.listconsoleflags(io.stdout)
178-- system.listconsoleflags(io.stderr)
179function sys.listtermflags(fh)
180 if fh == io.stdin then
181 print "------ STDIN FLAGS POSIX ------"
182 elseif fh == io.stdout then
183 print "------ STDOUT FLAGS POSIX ------"
184 elseif fh == io.stderr then
185 print "------ STDERR FLAGS POSIX ------"
186 end
187
188 local flags = assert(sys.tcgetattr(fh))
189 for _, flagtype in ipairs { "iflag", "oflag", "lflag" } do
190 local prefix = flagtype:sub(1,1):upper() .. "_" -- I_, O_, or L_, the constant prefixes
191 local out = {}
192 for k,v in pairs(sys) do
193 if type(k) == "string" and k:sub(1,2) == prefix then
194 if flags[flagtype]:has(v) then
195 out[#out+1] = string.format("%10d [x] %s",v:value(),k)
196 else
197 out[#out+1] = string.format("%10d [ ] %s",v:value(),k)
198 end
199 end
200 end
201 table.sort(out)
202 for k,v in pairs(out) do
203 print(v)
204 end
205 end
206end
207
208
209
210return sys