Compare commits

...

18 Commits

Author SHA1 Message Date
7566f9af82 liberty: comment on pthread_cancel
All checks were successful
Alpine 3.21 Success
OpenBSD 7.6 Success
2025-09-21 18:59:16 +02:00
7425355d01 liberty-xui: fix a new Fontconfig warning
All checks were successful
Alpine 3.21 Success
OpenBSD 7.6 Success
2025-08-02 18:22:00 +02:00
d8f785eae5 liberty-xdg: don't crash on missing X11 atoms
All checks were successful
Alpine 3.21 Success
OpenBSD 7.6 Success
They can be missing in bare configurations, such as Sway + XWayland.
2025-06-04 21:54:04 +02:00
31ae400852 LibertyXDR: update VIM syntax highlight file
All checks were successful
Alpine 3.21 Success
OpenBSD 7.6 Success
2025-05-07 19:47:43 +02:00
b69d3f8692 LibertyXDR: add support for default in unions 2025-05-07 19:42:46 +02:00
9a26284a64 wdye: clean up protected calls
Have a common way of catching Lua errors for resource cleanup purposes.
2025-01-15 02:21:23 +01:00
0f20cce9c8 wdye: pass script arguments
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2025-01-10 10:58:27 +01:00
017cb1d570 MPD client: tolerate usage while disconnected
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
If the client is password-protected, this will not automagically
make queued up commands work, but it's better than hitting
the poller assertion.
2025-01-08 08:07:46 +01:00
1642d387f3 wdye: rename the self-test
add_subdirectory imports it to parent projects, so be more indicative.
2025-01-08 06:24:05 +01:00
af889b733e wdye: ensure we find our own config.h
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2025-01-08 06:14:47 +01:00
51231d84ba wdye: clean up, add process.pid
All checks were successful
OpenBSD 7.5 Success
Alpine 3.20 Success
2025-01-07 03:16:37 +01:00
6c47e384f5 wdye: optionally produce asciicast v2 logs
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
I've been fairly disappointed with asciinema,
but it's slightly better than nothing.
2025-01-06 17:03:54 +01:00
914e743dc4 wdye: don't add the script path on error
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
Lua already provides this for us, including the line number.
2025-01-06 14:40:58 +01:00
37a8f16235 wdye: enable waiting for processes 2025-01-06 14:29:41 +01:00
9fe576ae9e wdye: read out the whole terminfo database
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
Also update LICENSE years.
2025-01-06 11:59:46 +01:00
5c02778ff8 wdye: improve portability
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2025-01-06 10:14:49 +01:00
e40d56152d Add an Expect-like tool
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
This is to provide an Expect utility with a minimal dependency tree
for C-based projects.  It also addresses some Tcl Expect design issues,
as perceived by me.
2025-01-06 08:30:14 +01:00
21379d4c02 Update README
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2025-01-01 23:36:55 +01:00
14 changed files with 1699 additions and 20 deletions

View File

@@ -1,4 +1,4 @@
Copyright (c) 2014 - 2024, Přemysl Eric Janouch <p@janouch.name> Copyright (c) 2014 - 2025, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted. purpose with or without fee is hereby granted.

View File

@@ -15,8 +15,8 @@ mess that are header files.
The API is intentionally unstable, which allows for easy refactoring. The API is intentionally unstable, which allows for easy refactoring.
All development is done on Linux, but other POSIX-compatible operating systems All development is done on Linux, but other POSIX-compatible operating systems
should be generally supported as well. They have an extremely low priority, should be generally supported as well. They have a lower priority, however,
however, and I'm not testing them at all, perhaps with the exception of macOS. and don't receive as much testing.
Tools Tools
----- -----
@@ -38,6 +38,7 @@ cmake-dump.awk::
help2adoc.awk:: help2adoc.awk::
Produces AsciiDoc manual pages from --version/--help output. Produces AsciiDoc manual pages from --version/--help output.
These can then be processed by _asciiman.awk_.
lxdrgen.awk:: lxdrgen.awk::
Protocol code generator for a variant of XDR, Protocol code generator for a variant of XDR,
@@ -67,6 +68,9 @@ lxdrgen-mjs.awk::
lxdrgen-swift.awk:: lxdrgen-swift.awk::
LibertyXDR backend for the Swift programming language. LibertyXDR backend for the Swift programming language.
wdye::
Compiled Lua-based Expect-like utility, intended purely for build checks.
Contributing and Support Contributing and Support
------------------------ ------------------------
Use https://git.janouch.name/p/liberty to report any bugs, request features, Use https://git.janouch.name/p/liberty to report any bugs, request features,

View File

@@ -1594,6 +1594,8 @@ mpd_client_parse_kv (char *line, char **value)
static void static void
mpd_client_update_poller (struct mpd_client *self) mpd_client_update_poller (struct mpd_client *self)
{ {
if (self->state != MPD_CONNECTED)
return;
poller_fd_set (&self->socket_event, poller_fd_set (&self->socket_event,
self->write_buffer.len ? (POLLIN | POLLOUT) : POLLIN); self->write_buffer.len ? (POLLIN | POLLOUT) : POLLIN);
} }

View File

@@ -86,24 +86,30 @@ xdg_xsettings_update (struct xdg_xsettings *self, Display *dpy)
// TODO: We're supposed to lock the server. // TODO: We're supposed to lock the server.
// TODO: We're supposed to trap X errors. // TODO: We're supposed to trap X errors.
char *selection = xstrdup_printf ("_XSETTINGS_S%d", DefaultScreen (dpy)); char *selection = xstrdup_printf ("_XSETTINGS_S%d", DefaultScreen (dpy));
Window owner Atom selection_atom = XInternAtom (dpy, selection, True);
= XGetSelectionOwner (dpy, XInternAtom (dpy, selection, True));
free (selection); free (selection);
if (!selection_atom)
return;
Window owner = XGetSelectionOwner (dpy, selection_atom);
if (!owner) if (!owner)
return; return;
Atom xsettings_atom = XInternAtom (dpy, "_XSETTINGS_SETTINGS", True);
if (!xsettings_atom)
return;
Atom actual_type = None; Atom actual_type = None;
int actual_format = 0; int actual_format = 0;
unsigned long nitems = 0, bytes_after = 0; unsigned long nitems = 0, bytes_after = 0;
unsigned char *buffer = NULL; unsigned char *buffer = NULL;
Atom xsettings = XInternAtom (dpy, "_XSETTINGS_SETTINGS", True);
int status = XGetWindowProperty (dpy, int status = XGetWindowProperty (dpy,
owner, owner,
xsettings, xsettings_atom,
0L, 0L,
LONG_MAX, LONG_MAX,
False, False,
xsettings, xsettings_atom,
&actual_type, &actual_type,
&actual_format, &actual_format,
&nitems, &nitems,
@@ -112,7 +118,7 @@ xdg_xsettings_update (struct xdg_xsettings *self, Display *dpy)
if (status != Success || !buffer) if (status != Success || !buffer)
return; return;
if (actual_type != xsettings if (actual_type != xsettings_atom
|| actual_format != 8 || actual_format != 8
|| nitems < 12) || nitems < 12)
goto fail; goto fail;

View File

@@ -1884,6 +1884,8 @@ x11_init (struct poller *poller, struct attrs *app_attrs, size_t app_attrs_len)
if (!(g_xui.dpy = XkbOpenDisplay if (!(g_xui.dpy = XkbOpenDisplay
(NULL, &g_xui.xkb_base_event_code, NULL, NULL, NULL, NULL))) (NULL, &g_xui.xkb_base_event_code, NULL, NULL, NULL, NULL)))
exit_fatal ("cannot open display"); exit_fatal ("cannot open display");
if (!XftInit (NULL))
print_warning ("Fontconfig initialization failed");
if (!XftDefaultHasRender (g_xui.dpy)) if (!XftDefaultHasRender (g_xui.dpy))
exit_fatal ("XRender is not supported"); exit_fatal ("XRender is not supported");
if (!(g_xui.x11_im = XOpenIM (g_xui.dpy, NULL, NULL, NULL))) if (!(g_xui.x11_im = XOpenIM (g_xui.dpy, NULL, NULL, NULL)))
@@ -1912,8 +1914,6 @@ x11_init (struct poller *poller, struct attrs *app_attrs, size_t app_attrs_len)
g_xui.x11_xsettings = xdg_xsettings_make (); g_xui.x11_xsettings = xdg_xsettings_make ();
xdg_xsettings_update (&g_xui.x11_xsettings, g_xui.dpy); xdg_xsettings_update (&g_xui.x11_xsettings, g_xui.dpy);
if (!FcInit ())
print_warning ("Fontconfig initialization failed");
if (!(g_xui.xft_fonts = x11_font_open (0))) if (!(g_xui.xft_fonts = x11_font_open (0)))
exit_fatal ("cannot open a font"); exit_fatal ("cannot open a font");

View File

@@ -1209,7 +1209,10 @@ async_make (struct async_manager *manager)
} }
/// Only allowed from the main thread once the job has been started but before /// Only allowed from the main thread once the job has been started but before
/// the results have been dispatched /// the results have been dispatched.
///
/// Note that it may in practice lead to memory leakage, although that's
/// an implementation issue: https://eissing.org/icing/posts/rip_pthread_cancel/
static void static void
async_cancel (struct async *self) async_cancel (struct async *self)
{ {

View File

@@ -93,10 +93,12 @@ and always-present field, which must be a tag *enum*:
case CAR: void; case CAR: void;
case LORRY: i8 axles; case LORRY: i8 axles;
case PLANE: i8 engines; case PLANE: i8 engines;
default: void;
}; };
All possible enumeration values must be named, and there is no *case* There is no *case* fall-through.
fall-through. Unless *default* is present, only the listed enumeration values are valid.
Any *default* must currently be empty.
Framing Framing
------- -------

View File

@@ -8,7 +8,7 @@ syn region libertyxdrBlockComment start=+/[*]+ end=+[*]/+
syn match libertyxdrComment "//.*" syn match libertyxdrComment "//.*"
syn match libertyxdrIdentifier "\<[[:alpha:]][[:alnum:]_]*\>" syn match libertyxdrIdentifier "\<[[:alpha:]][[:alnum:]_]*\>"
syn match libertyxdrNumber "\<0\>\|\(-\|\<\)[1-9][[:digit:]]*\>" syn match libertyxdrNumber "\<0\>\|\(-\|\<\)[1-9][[:digit:]]*\>"
syn keyword libertyxdrKeyword const enum struct union switch case syn keyword libertyxdrKeyword const enum struct union switch case default
syn keyword libertyxdrType bool u8 u16 u32 u64 i8 i16 i32 i64 string void syn keyword libertyxdrType bool u8 u16 u32 u64 i8 i16 i32 i64 string void
let b:current_syntax = "libertyxdr" let b:current_syntax = "libertyxdr"

View File

@@ -25,5 +25,7 @@ struct Struct {
union Onion switch (Enum tag) { union Onion switch (Enum tag) {
case NOTHING: case NOTHING:
void; void;
default:
void;
} o; } o;
}; };

View File

@@ -1,6 +1,6 @@
# lxdrgen.awk: an XDR-derived code generator for network protocols. # lxdrgen.awk: an XDR-derived code generator for network protocols.
# #
# Copyright (c) 2022 - 2023, Přemysl Eric Janouch <p@janouch.name> # Copyright (c) 2022 - 2025, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD # SPDX-License-Identifier: 0BSD
# #
# Usage: env LC_ALL=C awk -f lxdrgen.awk -f lxdrgen-{c,go,mjs}.awk \ # Usage: env LC_ALL=C awk -f lxdrgen.awk -f lxdrgen-{c,go,mjs}.awk \
@@ -218,7 +218,7 @@ function defstruct( name, d, cg) {
} }
function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i, function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i,
unseen, exhaustive) { unseen, defaulted, exhaustive) {
delete cg[0] delete cg[0]
delete scg[0] delete scg[0]
delete d[0] delete d[0]
@@ -249,9 +249,22 @@ function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i,
if (!unseen[tagvalue]--) if (!unseen[tagvalue]--)
fatal("no such value or duplicate case: " tagtype "." tagvalue) fatal("no such value or duplicate case: " tagtype "." tagvalue)
codegen_struct_tag(tag, scg) codegen_struct_tag(tag, scg)
} else if (accept("default")) {
if (tagvalue)
codegen_union_struct(name, tagvalue, cg, scg)
expect(accept(":"))
if (defaulted)
fatal("duplicate default")
tagvalue = ""
defaulted = 1
} else if (tagvalue) { } else if (tagvalue) {
if (readfield(d)) if (readfield(d))
codegen_struct_field(d, scg) codegen_struct_field(d, scg)
} else if (defaulted) {
if (readfield(d))
fatal("default must not contain fields")
} else { } else {
fatal("union fields must fall under a case") fatal("union fields must fall under a case")
} }
@@ -259,11 +272,17 @@ function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i,
if (tagvalue) if (tagvalue)
codegen_union_struct(name, tagvalue, cg, scg) codegen_union_struct(name, tagvalue, cg, scg)
# Unseen cases are simply not recognized/allowed. # Unseen cases are only recognized/allowed when default is present.
exhaustive = 1 exhaustive = 1
for (i in unseen) for (i in unseen)
if (i && unseen[i]) if (i && unseen[i]) {
exhaustive = 0 if (defaulted) {
codegen_struct_tag(tag, scg)
codegen_union_struct(name, i, cg, scg)
} else {
exhaustive = 0
}
}
Types[name] = "union" Types[name] = "union"
codegen_union(name, cg, exhaustive) codegen_union(name, cg, exhaustive)

42
tools/wdye/CMakeLists.txt Normal file
View File

@@ -0,0 +1,42 @@
cmake_minimum_required (VERSION 3.18)
project (wdye VERSION 1 DESCRIPTION "What did you expect?" LANGUAGES C)
set (CMAKE_C_STANDARD 99)
set (CMAKE_C_STANDARD_REQUIRED ON)
set (CMAKE_C_EXTENSIONS OFF)
# -Wunused-function is pretty annoying here, as everything is static
set (options -Wall -Wextra -Wno-unused-function)
add_compile_options ("$<$<CXX_COMPILER_ID:GNU>:${options}>")
add_compile_options ("$<$<CXX_COMPILER_ID:Clang>:${options}>")
set (CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../../cmake")
find_package (Curses)
find_package (PkgConfig REQUIRED)
pkg_search_module (lua REQUIRED
lua53 lua5.3 lua-5.3 lua54 lua5.4 lua-5.4 lua>=5.3)
option (WITH_CURSES "Offer terminal sequences using Curses" "${CURSES_FOUND}")
# -liconv may or may not be a part of libc
find_path (iconv_INCLUDE_DIRS iconv.h)
include_directories (BEFORE "${PROJECT_BINARY_DIR}" ${iconv_INCLUDE_DIRS})
file (CONFIGURE OUTPUT "${PROJECT_BINARY_DIR}/config.h" CONTENT [[
#define PROGRAM_NAME "${PROJECT_NAME}"
#define PROGRAM_VERSION "${PROJECT_VERSION}"
#cmakedefine WITH_CURSES
]])
add_executable (wdye wdye.c)
target_include_directories (wdye PUBLIC ${lua_INCLUDE_DIRS})
target_link_directories (wdye PUBLIC ${lua_LIBRARY_DIRS})
target_link_libraries (wdye PUBLIC ${lua_LIBRARIES})
if (WITH_CURSES)
target_include_directories (wdye PUBLIC ${CURSES_INCLUDE_DIRS})
target_link_libraries (wdye PUBLIC ${CURSES_LIBRARIES})
endif ()
add_test (NAME wdye COMMAND wdye "${PROJECT_SOURCE_DIR}/test.lua")
include (CTest)

33
tools/wdye/test.lua Normal file
View File

@@ -0,0 +1,33 @@
for k, v in pairs(wdye) do _G[k] = v end
-- The terminal echoes back, we don't want to read the same stuff twice.
local cat = spawn {"sh", "-c", "cat > /dev/null", environ={TERM="xterm"}}
assert(cat, "failed to spawn process")
assert(cat.term.key_left, "bad terminfo")
cat:send("Hello\r")
local m = expect(cat:exact {"Hello\r", function (p) return p[0] end})
assert(m == "Hello\r", "exact match failed, or value expansion mismatch")
local t = table.pack(expect(timeout {.5, 42}))
assert(#t == 1 and t[1] == 42, "timeout match failed, or value mismatch")
cat:send("abc123\r")
expect(cat:regex {"A(.*)3", nocase=true, function (p)
assert(p[0] == "abc123", "wrong regex group #0")
assert(p[1] == "bc12", "wrong regex group #1")
end})
assert(not cat:wait (true), "process reports exiting early")
-- Send EOF (^D), test method chaining.
cat:send("Closing...\r"):send("\004")
local v = expect(cat:eof {true},
cat:default {.5, function (p) error "expected EOF, got a timeout" end})
assert(cat.pid > 0, "process has no ID")
local s1, exit, signal = cat:wait ()
assert(s1 == 0 and exit == 0 and not signal, "unexpected exit status")
assert(cat.pid < 0, "process still has an ID")
local s2 = cat:wait (true)
assert(s1 == s2, "exit status not remembered")

146
tools/wdye/wdye.adoc Normal file
View File

@@ -0,0 +1,146 @@
wdye(1)
=======
:doctype: manpage
:manmanual: wdye Manual
:mansource: wdye {release-version}
Name
----
wdye - what did you expect: Lua-based Expect tool
Synopsis
--------
*wdye* _program.lua_
Description
-----------
*wdye* executes a Lua script, providing an *expect*(1)-like API targeted
at application testing.
API
---
This list is logically ordered. Uppercase names represent object types.
wdye.spawn {file [, arg1, ...] [, environ=env]}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Creates a new pseudoterminal, spawns the given program in it,
and returns a _process_ object. When *file* doesn't contain slashes,
the program will be searched for in _PATH_.
The *env* map may be used to override environment variables, notably _TERM_.
Variables evaluating to _false_ will be removed from the environment.
The program's whole process group receives SIGKILL when the _process_
is garbage-collected, unless *wait* has collected the process group leader.
wdye.expect ([pattern1, ...])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Waits until any pattern is ready, in order.
When no *timeout* (or *default*) patterns are included, one is added implicitly.
The function returns the matching _pattern_'s values, while replacing
any included functions with the results of their immediate evaluation,
passing the matching _pattern_ as their sole argument.
wdye.timeout {[timeout, ] [value1, ...]}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns a new timeout _pattern_. When no *timeout* is given, which is specified
in seconds, a default timeout value is assumed. Any further values
are remembered to be later processed by *expect*.
wdye.continue ()
~~~~~~~~~~~~~~~~
Raises a _nil_ error, which is interpreted by *expect* as a signal to restart
all processing.
PROCESS.buffer
~~~~~~~~~~~~~~
A string with the _process_' current read buffer contents.
PROCESS.pid
~~~~~~~~~~~
An integer with the _process_' process ID, or -1 if *wait* has collected it.
PROCESS.term
~~~~~~~~~~~~
A table with the _process_' *terminfo*(5) capabilities,
notably containing all **key_...** codes.
This functionality may not be enabled, then this table will always be empty.
PROCESS:send ([string, ...])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Writes the given strings to the _process_' terminal slave,
and returns the _process_ for method chaining.
Beware of echoing and deadlocks, as only *expect* can read from the _process_,
and thus consume the terminal slave's output queue.
PROCESS:regex {pattern [, nocase=true] [, notransfer=true] [, value1, ...]}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns a new regular expression _pattern_. The *pattern* is a POSIX
Extended Regular Expression. Whether it can match NUL bytes depends on your
system C library.
When the *nocase* option is _true_, the expression will be matched
case-insensitively.
Unless the *notransfer* option is _true_, all data up until the end of the match
will be erased from the _process_' read buffer upon a successful match.
PROCESS:exact {literal [, nocase=true] [, notransfer=true] [, value1, ...]}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns a new literal string _pattern_. This behaves as if the *literal*
had its ERE special characters quoted, and was then passed to *regex*.
This _pattern_ can always match NUL bytes.
PROCESS:eof {[notransfer=true, ] [value1, ...]}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns a new end-of-file _pattern_, which matches the entire read buffer
contents once the child process closes the terminal.
PROCESS:wait ([nowait])
~~~~~~~~~~~~~~~~~~~~~~~
Waits for the program to terminate, and returns three values:
a combined status as used by `$?` in shells,
an exit status, and a termination signal number.
One of the latter two values will be _nil_, as appropriate.
When the *nowait* option is _true_, the function returns immediately.
If the process hasn't terminated yet, the function then returns no values.
PROCESS:default {[timeout, ] [notransfer=true, ] [value1, ...]}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns a new _pattern_ combining *wdye.timeout* with *eof*.
PATTERN.process
~~~~~~~~~~~~~~~
A reference to the _pattern_'s respective process, or _nil_.
PATTERN[group]
~~~~~~~~~~~~~~
For patterns that can match data, the zeroth group will be the whole matched
input sequence.
For *regex* patterns, positive groups relate to regular expression subgroups.
Missing groups evaluate to _nil_.
Example
-------
for k, v in pairs(wdye) do _G[k] = v end
local rot13 = spawn {"tr", "A-Za-z", "N-ZA-Mn-za-m", environ={TERM="dumb"}}
rot13:send "Hello\r"
expect(rot13:exact {"Uryyb\r"})
Environment
-----------
*WDYE_LOGGING*::
When this environment variable is present, *wdye* produces asciicast v2
files for every spawned program, in the current working directory.
Reporting bugs
--------------
Use https://git.janouch.name/p/liberty to report bugs, request features,
or submit pull requests.
See also
--------
*expect*(1), *terminfo*(5), *regex*(7)

1420
tools/wdye/wdye.c Normal file

File diff suppressed because it is too large Load Diff