Compare commits

...

13 Commits

Author SHA1 Message Date
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
9268fb8eba help2adoc: fix nawk
All checks were successful
Alpine 3.20 Success
2024-12-31 20:34:48 +01:00
b01df19b80 asciiman: have fewer "unexpected EOF" situations
Some checks failed
Alpine 3.20 Scripts failed
Easily caused by the new help2adoc.
2024-12-31 20:25:51 +01:00
09e635cf97 Add a --help/--version to AsciiDoc convertor
liberty is now self-contained, from opt_handler to manual page.
2024-12-31 20:25:51 +01:00
7560e8700e cmake-parser: improve portability 2024-12-31 06:47:31 +01:00
11 changed files with 2120 additions and 22 deletions

View File

@@ -10,7 +10,7 @@ if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
endif ()
# Dependencies
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
set (CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
include (AddThreads)
find_package (PkgConfig REQUIRED)
@@ -35,7 +35,7 @@ foreach (extra iconv rt)
endforeach ()
# Build some unit tests
include_directories (${PROJECT_SOURCE_DIR})
include_directories ("${PROJECT_SOURCE_DIR}")
enable_testing ()
set (tests liberty proto xdg)
@@ -57,7 +57,7 @@ endforeach ()
# --- Tools --------------------------------------------------------------------
# Test the AsciiDoc manual page generator for a successful parse
set (ASCIIMAN ${PROJECT_SOURCE_DIR}/tools/asciiman.awk)
set (ASCIIMAN "${PROJECT_SOURCE_DIR}/tools/asciiman.awk")
add_custom_command (OUTPUT libertyxdr.7
COMMAND env LC_ALL=C awk -f ${ASCIIMAN}
"${PROJECT_SOURCE_DIR}/libertyxdr.adoc" > libertyxdr.7
@@ -65,10 +65,14 @@ add_custom_command (OUTPUT libertyxdr.7
COMMENT "Generating man page for libertyxdr" VERBATIM)
add_custom_target (docs ALL DEPENDS libertyxdr.7)
# Test the --help/--version to AsciiDoc convertor
add_test (test-help2adoc
env LC_ALL=C "${PROJECT_SOURCE_DIR}/tests/help2adoc.sh")
# Test CMake script parsing
add_test (test-cmake-parser
env LC_ALL=C awk -f ${PROJECT_SOURCE_DIR}/tools/cmake-parser.awk
-f ${PROJECT_SOURCE_DIR}/tools/cmake-dump.awk ${CMAKE_CURRENT_LIST_FILE})
env LC_ALL=C awk -f "${PROJECT_SOURCE_DIR}/tools/cmake-parser.awk"
-f "${PROJECT_SOURCE_DIR}/tools/cmake-dump.awk" ${CMAKE_CURRENT_LIST_FILE})
# Test protocol code generation
set (lxdrgen_outputs)
@@ -77,15 +81,15 @@ foreach (backend c cpp go mjs swift)
list (APPEND lxdrgen_outputs ${lxdrgen_base}.${backend})
add_custom_command (OUTPUT ${lxdrgen_base}.${backend}
COMMAND env LC_ALL=C awk
-f ${PROJECT_SOURCE_DIR}/tools/lxdrgen.awk
-f ${PROJECT_SOURCE_DIR}/tools/lxdrgen-${backend}.awk
-f "${PROJECT_SOURCE_DIR}/tools/lxdrgen.awk"
-f "${PROJECT_SOURCE_DIR}/tools/lxdrgen-${backend}.awk"
-v PrefixCamel=ProtoGen
${PROJECT_SOURCE_DIR}/tests/lxdrgen.lxdr
"${PROJECT_SOURCE_DIR}/tests/lxdrgen.lxdr"
> ${lxdrgen_base}.${backend}
DEPENDS
${PROJECT_SOURCE_DIR}/tools/lxdrgen.awk
${PROJECT_SOURCE_DIR}/tools/lxdrgen-${backend}.awk
${PROJECT_SOURCE_DIR}/tests/lxdrgen.lxdr
"${PROJECT_SOURCE_DIR}/tools/lxdrgen.awk"
"${PROJECT_SOURCE_DIR}/tools/lxdrgen-${backend}.awk"
"${PROJECT_SOURCE_DIR}/tests/lxdrgen.lxdr"
COMMENT "Generating test protocol code (${backend})" VERBATIM)
endforeach ()
add_custom_target (test-lxdrgen-outputs ALL DEPENDS ${lxdrgen_outputs})

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
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.
All development is done on Linux, but other POSIX-compatible operating systems
should be generally supported as well. They have an extremely low priority,
however, and I'm not testing them at all, perhaps with the exception of macOS.
should be generally supported as well. They have a lower priority, however,
and don't receive as much testing.
Tools
-----
@@ -36,6 +36,10 @@ cmake-dump.awk::
This can be used in conjunction with the previous script to dump CMake
scripts in a normalized format for further processing.
help2adoc.awk::
Produces AsciiDoc manual pages from --version/--help output.
These can then be processed by _asciiman.awk_.
lxdrgen.awk::
Protocol code generator for a variant of XDR,
which is link:libertyxdr.adoc[documented separately].
@@ -64,6 +68,9 @@ lxdrgen-mjs.awk::
lxdrgen-swift.awk::
LibertyXDR backend for the Swift programming language.
wdye::
Compiled Lua-based Expect-like utility, intended purely for build checks.
Contributing and Support
------------------------
Use https://git.janouch.name/p/liberty to report any bugs, request features,

214
tests/help2adoc.sh Executable file
View File

@@ -0,0 +1,214 @@
#!/bin/sh -e
# This test very exactly matches the output,
# but help2adoc is more or less feature-complete already.
self=$(realpath "$0")
help2adoc=$(realpath "$(dirname "$0")/../tools/help2adoc.awk")
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
test_oneline_help() {
cat <<END
Usage: $self [--brightness [+-]BRIGHTNESS] [--input NAME] [--restart]
END
}
test_oneline_version() {
cat <<'END'
eizoctl 1.0
END
}
test_oneline_out() {
cat <<'END'
eizoctl(1)
==========
:doctype: manpage
:manmanual: eizoctl Manual
:mansource: eizoctl 1.0
Name
----
eizoctl - manual page for eizoctl 1.0
Synopsis
--------
*eizoctl* [**--brightness** [+-]__BRIGHTNESS__] [**--input** __NAME__] [**--restart**]
END
}
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
test_simple_help() {
cat <<'END'
Usage: elksmart-comm [OPTION]... [COMMAND...]
Usage: elksmart-comm
Transmit or receive infrared commands.
-d, --debug elksmart-comm will run in debug mode
-f, --frequency HZ frequency (38000 Hz by default)
-n, --nec use the NEC transmission format
-h, --help display this help and exit
-V, --version output version information and exit
END
}
test_simple_version() {
cat <<'END'
elksmart-comm (usb-drivers) dev
END
}
test_simple_out() {
cat <<'END'
elksmart-comm(1)
================
:doctype: manpage
:manmanual: elksmart-comm Manual
:mansource: usb-drivers dev
Name
----
elksmart-comm - manual page for elksmart-comm dev
Synopsis
--------
*elksmart-comm* [__OPTION__]... [__COMMAND__...]
*elksmart-comm*
Description
-----------
Transmit or receive infrared commands.
*-d*, **--debug**::
**elksmart-comm** will run in debug mode
*-f*, **--frequency** __HZ__::
frequency (38000 Hz by default)
*-n*, **--nec**::
use the NEC transmission format
*-h*, **--help**::
display this help and exit
*-V*, **--version**::
output version information and exit
END
}
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
test_wild_help() {
cat <<'END'
Usage:
wild [option]… <command>… — Be wild
What's happening?
-f, --frequency hz-2-foo frequency to --foo at
--foo=bar
Foobar.
Boo far.
Subsection:
--help
--version
Oh my.
Major section:
And now for something completely different.
Very wild
END
}
test_wild_version() {
cat <<'END'
wild 1
Copies left and right.
END
}
test_wild_out() {
cat <<'END'
wild(1)
=======
:doctype: manpage
:manmanual: wild Manual
:mansource: wild 1
Name
----
wild - manual page for wild 1
Synopsis
--------
*wild* [__option__]... <__command__>...
Description
-----------
Be **wild**
What's happening?
*-f*, **--frequency** __hz-2-foo__::
frequency to **--foo** at
*--foo*=__bar__::
Foobar.
Boo far.
Subsection
~~~~~~~~~~
*--help*::
*--version*::
Oh my.
Major section
-------------
And now for something completely different.
Very wild
END
}
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
run() {
echo "-- help2adoc/$1"
local selfquoted=$(echo "$self" | sed 's/\\/&&/g')
local output=$(TEST=$1 awk -f "$help2adoc" -v Target="$selfquoted")
local expect="$($1_out)"
if [ "$output" = "$expect" ]
then return
fi
echo "== Expected"
sed 's/^/ /' <<-END
$expect
END
echo "== Received"
sed 's/^/ /' <<-END
$output
END
exit 1
}
if [ -z "$TEST" ]
then
run test_oneline
run test_simple
run test_wild
echo "-- OK"
elif [ "$1" = "--help" ]
then ${TEST}_help
elif [ "$1" = "--version" ]
then ${TEST}_version
else
echo "Wrong usage"
exit 1
fi

View File

@@ -200,7 +200,7 @@ function inline(line) {
}
# Returns 1 iff the left-over $0 should be processed further.
function process(firstline, posattrs, namedattrs) {
function process(firstline, posattrs, namedattrs, ok) {
if (readattribute(firstline))
return 0
if (getline <= 0) {
@@ -281,9 +281,11 @@ function process(firstline, posattrs, namedattrs) {
while ($0) {
sub(/^[[:space:]]+/, "")
sub(/^[+]$/, "")
if (!process($0) && getline <= 0)
fatal("unexpected EOF")
if (match($0, /^[[:space:]]*[*][[:space:]]+/))
if (!process($0) && (ok = getline) <= 0) {
if (ok < 0)
fatal("getline failed")
$0 = ""
} else if (match($0, /^[[:space:]]*[*][[:space:]]+/))
break
}
print ".RE"
@@ -318,9 +320,11 @@ function process(firstline, posattrs, namedattrs) {
while ($0) {
sub(/^[[:space:]]+/, "")
sub(/^[+]$/, "")
if (!process($0) && getline <= 0)
fatal("unexpected EOF")
if (match($0, /::$/))
if (!process($0) && (ok = getline) <= 0) {
if (ok < 0)
fatal("getline failed")
$0 = ""
} else if (match($0, /::$/))
break
}
print ".RE"

View File

@@ -75,7 +75,7 @@ function line_ending() {
# it doesn't seem to be worth the effort.
function expand(s, v) {
v = s
while (match(v, /\\*[$](|ENV|CACHE)[{]/)) {
while (match(v, /\\*[$](ENV|CACHE)?[{]/)) {
if (index(substr(v, RSTART), "$") % 2 != 0) {
warning("variable expansion is not supported: " s)
return s

234
tools/help2adoc.awk Normal file
View File

@@ -0,0 +1,234 @@
# help2adoc.awk: convert --version/--help to AsciiDoc manual pages
#
# Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# Usage: awk -f help2adoc.awk -v Target=cat
#
# This is not intended to produce great output, merely useful output,
# if only because there is no real standard of what the input should look like.
#
# The only target that needs to work is liberty's own opt_handler.
# The expected input format is roughly that of GNU utilites.
function fatal(message) {
print "// " message
print "fatal error: " message > "/dev/stderr"
exit 1
}
# The input model of this script is that function take the next line on $0,
# read further lines as necessary, and leave the next line in $0 again.
function readline( ok) {
if ((ok = (Command | getline)) < 0)
fatal("read error")
if (!ok)
exit
}
function emboldenoptions(line) {
# -N, --newer=DATE-OR-FILE, --after-date=DATE-OR-FILE
sub(/^-[^-=,[:space:]{[<]/, "*&*", line)
while (match(line, /[^-_[:alnum:]*'+]-[^-=,[:space:]{[<]/)) {
line = substr(line, 1, RSTART) \
"**" substr(line, RSTART + 1, RLENGTH - 1) "**" \
substr(line, RSTART + RLENGTH)
}
sub(/^--[-_[:alnum:]]+/, "*&*", line)
while (match(line, /[^-_[:alnum:]*'+]--[-_[:alnum:]]+/)) {
line = substr(line, 1, RSTART) \
"**" substr(line, RSTART + 1, RLENGTH - 1) "**" \
substr(line, RSTART + RLENGTH)
}
return line
}
function formatinline(line, programname, last, i) {
# Go the extra step of emboldening the program name at word boundaries.
programname = ProgramName
gsub(/[][\\.^$(){}|*+?]/, "\\\\&", programname)
if (match(line, "^" programname "[^-_[:alnum:]*'+/]")) {
line = "**" substr(line, RSTART, RLENGTH - 1) "**" \
substr(line, RSTART + RLENGTH - 1)
}
while (match(line, "[^-_[:alnum:]*'+/]" programname "[^-_[:alnum:]*'+/]")) {
line = substr(line, 1, RSTART) \
"**" substr(line, RSTART + 1, RLENGTH - 2) "**" \
substr(line, RSTART + RLENGTH - 1)
}
if (match(line, "[^-_[:alnum:]*'+/]" programname "$")) {
line = substr(line, 1, RSTART) \
"**" substr(line, RSTART + 1, RLENGTH - 1) "**"
}
return emboldenoptions(line)
}
function printusage(usage, description) {
gsub(/…/, "...", usage)
gsub(/—|/, "-", usage)
# --help output will more likely than not simply include argv[0],
# or perhaps program_invocation_short_name (not addressed here).
if (substr(usage, 1, length(Target) + 1) == Target " ")
usage = ProgramName substr(usage, length(Target) + 1)
# A lot of GNOME software includes the description here.
if (match(usage, / +- +/) && usage !~ / - [^[:alnum:]]/) {
description = substr(usage, RSTART + RLENGTH)
usage = substr(usage, 1, RSTART - 1)
}
while (match(usage, /[^-_[:alnum:]*'+.][[:alnum:]][-_[:alnum:]]+/)) {
usage = substr(usage, 1, RSTART) \
"__" substr(usage, RSTART + 1, RLENGTH - 1) "__" \
substr(usage, RSTART + RLENGTH)
}
sub(/^[^[:space:]]+/, "*&*", usage)
print emboldenoptions(usage)
print ""
if (description) {
flushsections()
print formatinline(description)
print ""
}
}
# We're going with Setext headers, because that's what asciiman.awk supports.
function printheader(text, underline) {
print text
gsub(/./, underline, text)
print text
}
BEGIN {
if (!Target)
fatal("missing Target")
TargetQuoted = Target
gsub(/'/, "'\\''", TargetQuoted)
TargetQuoted = "'" TargetQuoted "'"
# Remaining --version lines could be about copyright (GNU),
# or something else entirely.
Command = TargetQuoted " --version"
if ((Command | getline) > 0) {
# GNU --version output can place the package name in parentheses.
Package = $0
if (match($0, /[[:space:]][(][^)]*[)]/)) {
Package = substr($0, RSTART + 2, RLENGTH - 3) \
substr($0, RSTART + RLENGTH)
sub(/[[:space:]]+[(][^)]*[)]/, "")
}
Version = $0
sub(/[[:space:]]+[^[:space:]]+$/, "")
Name = $0
} else {
fatal("failed to get --version output")
}
if (Name !~ /[[:space:]]/)
ProgramName = Name
else if (match(Target, /[^\/]+$/))
ProgramName = substr(Target, RSTART, RLENGTH)
printheader(ProgramName "(1)", "=")
print ":doctype: manpage"
print ":manmanual: " Name " Manual"
print ":mansource: " Package
print ""
printheader("Name", "-")
print ProgramName " - manual page for " Version
print ""
close(Command)
Command = TargetQuoted " --help"
if ((Command | getline) <= 0)
fatal("failed to get --help output")
NextSection = "Description"
NextSubsection = ""
# The SYNOPSIS section is mandatory, so just put it there.
printheader("Synopsis", "-")
while (1) {
if (match($0, /^[Uu]sage:[[:space:]]*/)) {
if (($0 = substr($0, RSTART + RLENGTH)))
printusage($0)
} else if (match($0, /^[[:space:]]+/) && !/^[[:space:]]*-/) {
if (($0 = substr($0, RSTART + RLENGTH)))
printusage($0)
} else if ($0) {
break
}
readline()
}
while (1) {
if (match($0, /^[[:alpha:]][-[:alnum:][:space:]]+:$/)) {
# We don't flush sections here,
# so that we don't unnecessarily enforce DESCRIPTION first.
NextSection = substr($0, RSTART, RLENGTH - 1)
} else if (match($0, /^ [[:alpha:]][-[:alnum:][:space:]]+:$/)) {
flushsections()
NextSubsection = substr($0, RSTART + 1, RLENGTH - 2)
} else if (match($0, /^ +-/)) {
flushsections()
parseoption(substr($0, RSTART + RLENGTH - 1))
continue
} else if ($0) {
flushsections()
# That will be probably interpreted as a literal block.
if (!/^[[:space:]]/)
$0 = formatinline($0)
print
} else {
print
}
readline()
}
}
function flushsections() {
if (NextSection) {
print ""
printheader(NextSection, "-")
NextSection = ""
}
if (NextSubsection) {
print ""
printheader(NextSubsection, "~")
NextSubsection = ""
}
}
function parseoption(line, usage) {
# Often enough you will see it separated with only one space,
# which will simply not work for us.
if (match(line, /[[:space:]]{2,}/)) {
usage = substr(line, 1, RSTART - 1)
line = substr(line, RSTART + RLENGTH)
} else {
usage = line
line = ""
}
usage = emboldenoptions(usage)
while (match(usage, /[=<, ][[:alnum:]][-_[:alnum:]]*/)) {
usage = substr(usage, 1, RSTART) \
"__" substr(usage, RSTART + 1, RLENGTH - 1) "__" \
substr(usage, RSTART + RLENGTH)
}
print ""
print usage "::"
if (line)
print "\t" formatinline(line)
readline()
while (match($0, /^ +[^-[:space:]]|^ {7,}./)) {
print "\t" formatinline(substr($0, RSTART + RLENGTH - 1))
readline()
}
}

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 simple 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)

1414
tools/wdye/wdye.c Normal file

File diff suppressed because it is too large Load Diff