diff --git a/CMakeLists.txt b/CMakeLists.txt index 451e76b..543e0c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,14 @@ endif () # For custom modules set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake) +# Collect important build toggles for our simple preprocessor +# (cpp(1) isn't part of POSIX, otherwise we could reuse config.h) +set (options) +macro (add_option variable help value) + option (${ARGV}) + list (APPEND options "${variable}=$") +endmacro () + # Dependencies find_package (Ncursesw REQUIRED) find_package (PkgConfig REQUIRED) @@ -19,7 +27,7 @@ pkg_check_modules (curl REQUIRED libcurl) include (AddThreads) find_package (Termo QUIET NO_MODULE) -option (USE_SYSTEM_TERMO +add_option (USE_SYSTEM_TERMO "Don't compile our own termo library, use the system one" ${Termo_FOUND}) if (USE_SYSTEM_TERMO) if (NOT Termo_FOUND) @@ -41,7 +49,7 @@ else () endif () pkg_check_modules (fftw fftw3 fftw3f) -option (WITH_FFTW "Use FFTW to enable spectrum visualisation" ${fftw_FOUND}) +add_option (WITH_FFTW "Use FFTW to enable spectrum visualisation" ${fftw_FOUND}) if (WITH_FFTW) if (NOT fftw_FOUND) message (FATAL_ERROR "FFTW not found") @@ -50,7 +58,8 @@ if (WITH_FFTW) endif () pkg_check_modules (libpulse libpulse) -option (WITH_PULSE "Enable control of PulseAudio sink volume" ${libpulse_FOUND}) +add_option (WITH_PULSE + "Enable PulseAudio sink volume control" ${libpulse_FOUND}) if (WITH_PULSE) if (NOT libpulse_FOUND) message (FATAL_ERROR "libpulse not found") @@ -59,7 +68,7 @@ if (WITH_PULSE) endif () pkg_check_modules (x11 x11 xrender xft fontconfig) -option (WITH_X11 "Use FFTW to enable spectrum visualisation" ${x11_FOUND}) +add_option (WITH_X11 "Use FFTW to enable spectrum visualisation" ${x11_FOUND}) if (WITH_X11) if (NOT x11_FOUND) message (FATAL_ERROR "Some X11 libraries were not found") @@ -99,20 +108,13 @@ configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h) include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR}) -# Assuming a Unix-compatible system with a standalone preprocessor set (actions_list ${PROJECT_SOURCE_DIR}/nncmpp.actions) +set (actions_awk ${PROJECT_SOURCE_DIR}/nncmpp.actions.awk) set (actions ${PROJECT_BINARY_DIR}/nncmpp-actions.h) add_custom_command (OUTPUT ${actions} - COMMAND cpp -I${PROJECT_BINARY_DIR} -P ${actions_list} - | grep . | tr [[\n]] ^ | sed -ne [[h; s/,[^^]*/,/g]] -e [[s/$/COUNT/]] - -e [[s/[^^]*/\tACTION_&/g]] -e [[s/.*/enum action {\n&\n};\n/p]] - -e [[g; s/,[^^]*//g; y/_/-/]] -e [[s/[^^]\{1,\}/\t"&",/g]] - -e [[s/.*/static const char *g_action_names[] = {\n&};\n/p]] - -e [[g; s/[^^]*, *//g;]] -e [[s/[^^]\{1,\}/\t"&",/g]] - -e [[s/.*/static const char *g_action_descriptions[] = {\n&};/p]] - | tr ^ [[\n]] > ${actions} - COMMAND test -s ${actions} - DEPENDS ${actions_list} ${PROJECT_BINARY_DIR}/config.h VERBATIM) + COMMAND env LC_ALL=C ${options} + awk -f ${actions_awk} ${actions_list} > ${actions} + DEPENDS ${actions_awk} ${actions_list} VERBATIM) # Build the main executable and link it add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c ${actions}) diff --git a/nncmpp.actions b/nncmpp.actions index 38a8f76..7d0662d 100644 --- a/nncmpp.actions +++ b/nncmpp.actions @@ -1,5 +1,3 @@ -#include "config.h" - NONE, Do nothing QUIT, Quit @@ -28,11 +26,11 @@ MPD_CONSUME, Toggle consume MPD_UPDATE_DB, Update MPD database MPD_COMMAND, Send raw command to MPD -#ifdef WITH_PULSE +.ifdef WITH_PULSE PULSE_VOLUME_UP, Increase PulseAudio volume PULSE_VOLUME_DOWN, Decrease PulseAudio volume PULSE_MUTE, Toggle PulseAudio sink mute -#endif +.endif CHOOSE, Choose item DELETE, Delete item diff --git a/nncmpp.actions.awk b/nncmpp.actions.awk new file mode 100644 index 0000000..b4d7eaf --- /dev/null +++ b/nncmpp.actions.awk @@ -0,0 +1,106 @@ +# nncmpp.actions.awk: produce C code for a list of user actions +# +# Copyright (c) 2022, Přemysl Eric Janouch +# SPDX-License-Identifier: 0BSD +# +# Usage: env LC_ALL=C A=0 B=1 awk -f nncmpp.actions.awk \ +# nncmpp.actions > nncmpp-actions.h + +# --- Preprocessor ------------------------------------------------------------- + +function fatal(message) { + print "// " FILENAME ":" FNR ": fatal error: " message + print FILENAME ":" FNR ": fatal error: " message > "/dev/stderr" + exit 1 +} + +function condition(pass, passing, a, i) { + split(substr($0, RSTART + RLENGTH), a, /[[:space:]]+/) + if (!(1 in a)) + fatal("missing condition") + + passing = 0 + for (i in a) + if (a[i] && !pass == !ENVIRON[a[i]]) + passing = 1 + + while (getline > 0) { + if (match($0, /^[[:space:]]*[.]endif[[:space:]]*$/)) + return 1 + + if (match($0, /^[[:space:]]*[.]else[[:space:]]*$/)) + passing = !passing + else if (!directive() && passing) + process() + } + + fatal("unterminated condition body") +} + +# Multiple arguments mean logical OR, multiple directives mean logical AND. +# Similar syntax is also used by Exim, BSD make, or various assemblers. +# +# Looking at what others have picked for their preprocessor syntax: +# {OpenGL, FreeBASIC} reuse #ifdef, which would be confusing with C code around, +# {Mental Ray, RapidQ and UniVerse BASIC} use $ifdef, NSIS has !ifdef, +# and Verilog went for `ifdef. Not much more can be easily found. +function directive() { + sub(/#.*/, "") + if (match($0, /^[[:space:]]*[.]ifdef[[:space:]]+/)) + return condition(1) + if (match($0, /^[[:space:]]*[.]ifndef[[:space:]]+/)) + return condition(0) + if (/^[[:space:]]*[.]/) + fatal("unexpected or unsupported directive") + return 0 +} + +!directive() { + process() +} + +# --- Postprocessor ------------------------------------------------------------ + +function strip(string) { + gsub(/^[[:space:]]*|[[:space:]]*$/, "", string) + return string +} + +function process( constant, name, description) { + if (match($0, /,/)) { + constant = name = strip(substr($0, 1, RSTART - 1)) + description = strip(substr($0, RSTART + RLENGTH)) + gsub(/_/, "-", name) + + N++ + Constants[N] = constant + Names[N] = tolower(name) + Descriptions[N] = description + } else if (/[^[:space:]]/) { + fatal("invalid action definition syntax") + } +} + +function tocstring(string) { + gsub(/\\/, "\\\\", string) + gsub(/"/, "\\\"", string) + return "\"" string "\"" +} + +END { + print "enum action {" + for (i in Constants) + print "\t" "ACTION_" Constants[i] "," + print "\t" "ACTION_COUNT" + print "};" + print "" + print "static const char *g_action_names[] = {" + for (i in Names) + print "\t" tocstring(Names[i]) "," + print "};" + print "" + print "static const char *g_action_descriptions[] = {" + for (i in Descriptions) + print "\t" tocstring(Descriptions[i]) "," + print "};" +} diff --git a/nncmpp.c b/nncmpp.c index b96191b..74f0a51 100644 --- a/nncmpp.c +++ b/nncmpp.c @@ -4168,9 +4168,8 @@ help_tab_on_action (enum action action) if (action == ACTION_DESCRIBE) { - char *name = xstrdup (g_action_names[a]); - cstr_transform (name, tolower_ascii); - app_show_message (xstrdup ("Configuration name: "), name); + app_show_message (xstrdup ("Configuration name: "), + xstrdup (g_action_names[a])); return true; } if (action != ACTION_CHOOSE || a == ACTION_CHOOSE /* avoid recursion */)