diff options
author | Přemysl Eric Janouch <p@janouch.name> | 2024-12-30 23:14:10 +0100 |
---|---|---|
committer | Přemysl Eric Janouch <p@janouch.name> | 2024-12-31 20:25:51 +0100 |
commit | 09e635cf97a1cc84d2d3110d7a5e2d9d45f37e32 (patch) | |
tree | b8457b1871929cf84806a043e94e26da6ece93a2 | |
parent | 7560e8700e2c72cd4a11cfe818907bd9da76e800 (diff) | |
download | liberty-09e635cf97a1cc84d2d3110d7a5e2d9d45f37e32.tar.gz liberty-09e635cf97a1cc84d2d3110d7a5e2d9d45f37e32.tar.xz liberty-09e635cf97a1cc84d2d3110d7a5e2d9d45f37e32.zip |
Add a --help/--version to AsciiDoc convertor
liberty is now self-contained, from opt_handler to manual page.
-rw-r--r-- | CMakeLists.txt | 26 | ||||
-rw-r--r-- | README.adoc | 3 | ||||
-rwxr-xr-x | tests/help2adoc.sh | 214 | ||||
-rw-r--r-- | tools/help2adoc.awk | 234 |
4 files changed, 466 insertions, 11 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e04875..2e8520e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/README.adoc b/README.adoc index 20a4612..d6f6252 100644 --- a/README.adoc +++ b/README.adoc @@ -36,6 +36,9 @@ 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. + lxdrgen.awk:: Protocol code generator for a variant of XDR, which is link:libertyxdr.adoc[documented separately]. diff --git a/tests/help2adoc.sh b/tests/help2adoc.sh new file mode 100755 index 0000000..393c6a9 --- /dev/null +++ b/tests/help2adoc.sh @@ -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 diff --git a/tools/help2adoc.awk b/tools/help2adoc.awk new file mode 100644 index 0000000..b36753f --- /dev/null +++ b/tools/help2adoc.awk @@ -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() + } +} |