diff options
36 files changed, 2601 insertions, 676 deletions
@@ -4,13 +4,16 @@ *~ *.orig -# Files generated by aclocal, autoconf, and automake +# Files generated by gettextize, aclocal, autoconf, and automake +/ABOUT-NLS /aclocal.m4 /autom4te.cache/ /configure /config.h.in -/build-aux/ +/build-aux/* +!/build-aux/tap-driver.sh /ChangeLog +/FORMATS /INSTALL Makefile.in @@ -26,6 +29,9 @@ Makefile .dirstamp *.o /version.c -/@ +/atsign +/test-suite.log /atsign-*.tar* /atsign-*/ +/MD5SUMS +/SHA256SUMS diff --git a/Makefile.am b/Makefile.am index ecdbc4c..b53c109 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ # Process this file with automake to produce an input makefile. # -# Copyright (C) 2013, 2014, 2017, 2018, 2021 P. J. McDermott +# Copyright (C) 2013, 2014, 2017, 2018, 2021, 2022 P. J. McDermott # # This file is part of @ # @@ -40,33 +40,39 @@ endif # * Our release target PACKAGE_STRING = $(PACKAGE_NAME) $(PACKAGE_VERSION)$(PACKAGE_VERSION_GIT) -bin_PROGRAMS = @ +# Automake lib/am/check.am since version 1.16.3 defines this with single quotes, +# which breaks PACKAGE_VERSION_GIT above. +AM_TESTSUITE_SUMMARY_HEADER = " for $(PACKAGE_STRING)" -@_SOURCES = -@_CFLAGS = \ +bin_PROGRAMS = atsign + +atsign_SOURCES = +atsign_CFLAGS = \ $(WARN_CFLAGS) \ $(ASAN_CFLAGS) -@_CPPFLAGS = \ +atsign_CPPFLAGS = \ $(SDL_CFLAGS) \ $(SDL_TTF_CFLAGS) \ -DABS_BUILDDIR=\"$(abs_builddir)\" \ - -DFONTSDIR=\"$(fontsdir)\" -@_LDADD = \ + -DLOCALEDIR=\"$(localedir)\" +atsign_LDADD = \ $(ASAN_CFLAGS) \ $(SDL_LIBS) \ $(SDL_TTF_LIBS) -@_LINK = $(LINK) version.c -EXTRA_@_DEPENDENCIES = version.c +atsign_LINK = $(LINK) version.c +EXTRA_atsign_DEPENDENCIES = version.c CLEANFILES = version.c CONFIG_CLEAN_FILES = -EXTRA_DIST = autogen.sh +EXTRA_DIST = build-aux/config.rpath autogen.sh V_REDIR = $(V_REDIR_$(V)) V_REDIR_ = $(V_REDIR_$(AM_DEFAULT_VERBOSITY)) V_REDIR_0 = 1>/dev/null 2>&1 V_REDIR_1 = -version.c: $(@_OBJECTS) $(@_DEPENDENCIES) +SUFFIXES = + +version.c: $(atsign_OBJECTS) $(atsign_DEPENDENCIES) $(AM_V_GEN)printf 'const char *PACKAGE_VERSION_GIT = "%s";\n' \ "$(PACKAGE_VERSION_GIT)" >version.c @@ -77,15 +83,65 @@ really-clean: distclean $(srcdir)/build-aux/ $(srcdir)/INSTALL $(srcdir)/Makefile.in \ $(srcdir)/ChangeLog +if ENABLE_SYMLINK +install-exec-hook: + @set -e; \ + dir="$(DESTDIR)$(bindir)"; \ + dst="$$(printf 'atsign' | \ + sed '$(transform); s/$$/$(EXEEXT)/')"; \ + src='@$(EXEEXT)'; \ + echo " (cd '$${dir}' && $(LN_S) '$${dst}' '$${src}')"; \ + (cd "$${dir}" && $(LN_S) "$${dst}" "$${src}") +install-data-hook: + @set -e; \ + dir="$(DESTDIR)$(man1dir)"; \ + dst="$$(printf 'atsign' | \ + sed '$(transform); s/$$/$(EXEEXT)/').1"; \ + src='@$(EXEEXT).1'; \ + echo " (cd '$${dir}' && $(LN_S) '$${dst}' '$${src}')"; \ + (cd "$${dir}" && $(LN_S) "$${dst}" "$${src}") +uninstall-hook: + echo " rm -f '$(DESTDIR)$(bindir)/@$(EXEEXT)'" + rm -f "$(DESTDIR)$(bindir)/@$(EXEEXT)" +endif + +if ENABLE_TESTS +if CROSS_COMPILING dist-hook: - set -e; \ - if [ -d '$(srcdir)/.git' ]; then \ - printf 'Generated file. Do not edit.\n\n' \ - >'$(distdir)/ChangeLog~'; \ - GIT_DIR='$(srcdir)/.git' git log --stat --color=never \ - >>'$(distdir)/ChangeLog~'; \ - mv '$(distdir)/ChangeLog~' '$(distdir)/ChangeLog'; \ +else !CROSS_COMPILING +dist-hook: atsign$(EXEEXT) +endif !CROSS_COMPILING +else !ENABLE_TESTS +dist-hook: +endif !ENABLE_TESTS +if IN_GIT + printf 'Generated file. Do not edit.\n\n' 1>'$(distdir)/ChangeLog~' + GIT_DIR='$(srcdir)/.git' git log --stat --color=never \ + 1>>'$(distdir)/ChangeLog~' + mv '$(distdir)/ChangeLog~' '$(distdir)/ChangeLog' +endif +if ENABLE_TESTS +if CROSS_COMPILING + if [ -f 'FORMATS' ]; then \ + cp -p 'FORMATS' '$(distdir)/FORMATS'; \ + else \ + printf 'Error: "FORMATS" file cannot be generated when %s' \ + 'cross compiling' 1>&2; \ + exit 1; \ fi +else !CROSS_COMPILING + $(builddir)/atsign$(EXEEXT) -d '1969-12-31 19:00:01' -F \ + 1>'$(distdir)/FORMATS' +endif !CROSS_COMPILING +else !ENABLE_TESTS + if [ -f 'FORMATS' ]; then \ + cp -p 'FORMATS' '$(distdir)/FORMATS'; \ + else \ + printf 'Error: "FORMATS" file cannot be generated without %s' \ + 'test suite support' 1>&2; \ + exit 1; \ + fi +endif !ENABLE_TESTS release: sed "s/^Released: ????-??-??\$$/Released: $$(date '+%Y-%m-%d')/" \ @@ -112,3 +168,7 @@ release: files@files.pehjota.net:files/pub/$(PACKAGE)/$(VERSION)/ include $(top_srcdir)/src/local.mk +include $(top_srcdir)/man/local.mk +include $(top_srcdir)/tests/local.mk + +SUBDIRS = . po @@ -8,82 +8,22 @@ commands, e.g.: @ '19 January 2038 03:14:07'; halt @ 9:00am mon; printf '\a' +@ is similar to the standard sleep utility except that it accepts a time +and optional date instead of a delay duration. @ is distinct from the +at daemon package by Jose M Calhariz in that @ is not a daemon that runs +jobs in the background; instead it delays subsequent commands in a list, +allowing such commands to remain connected to a controlling terminal for +user interaction. + Date/Time Formats ----------------- -@ currently supports 704 date/time formats, any one of which may be -specified on the command line in one or more arguments. All spaces -shown below are optional. - -A time may be specified in any of the following eight formats: - - * 7:00:01 PM - * 19:00:01 - * 7:00 PM - * 19:00 - * 70001 PM - * 190001 - * 700 PM - * 1900 - -A date may be omitted or specified (before or after the time) in any of -the following 43 formats: - - * Wed - * 1969-12-31 - * 1969/12/31 - * 12-31-1969 - * 12-31 - * 12/31/1969 - * 12/31 - * 31 Dec 1969 - * 31 Dec - * 31-Dec-1969 - * 31-Dec - * 31/Dec/1969 - * 31/Dec - * Wed 31 Dec 1969 - * Wed 31 Dec - * Wed 31-Dec-1969 - * Wed 31-Dec - * Wed 31/Dec/1969 - * Wed 31/Dec - * Wed, 31 Dec 1969 - * Wed, 31 Dec - * Wed, 31-Dec-1969 - * Wed, 31-Dec - * Wed, 31/Dec/1969 - * Wed, 31/Dec - * Dec 31, 1969 - * Dec 31 - * Dec-31-1969 - * Dec-31 - * Dec/31/1969 - * Dec/31 - * Wed Dec 31, 1969 - * Wed Dec 31 - * Wed Dec-31-1969 - * Wed Dec-31 - * Wed Dec/31/1969 - * Wed Dec/31 - * Wed, Dec 31, 1969 - * Wed, Dec 31 - * Wed, Dec-31-1969 - * Wed, Dec-31 - * Wed, Dec/31/1969 - * Wed, Dec/31 - -Additionally, the following nine date/time formats are supported: +@ currently supports 4341 date/time formats, any one of which may be +specified on the command line in one or more arguments. The date may be +omitted or specified before or after the time. - * 1969-12-31T19:00:01 - * 1969-12-31T19:00 - * 19691231T190001 - * 19691231T1900 - * 19691231 70001 PM - * 19691231 190001 - * 19691231 700 PM - * 19691231 1900 - * Wed Dec 31 19:00:01 1969 +See the `FORMATS` file for a list of supported formats. All spaces +shown are optional. Development ----------- @@ -0,0 +1,4 @@ +try to fix dst bug +test with other libc's (newlib? musl? fbsd? obsd?) +`./@ 7:00 PM` doesn't work with musl strptime() +test other times @@ -2,7 +2,7 @@ # # Script to generate the build system. # -# Copyright (C) 2013 P. J. McDermott +# Copyright (C) 2013, 2019 P. J. McDermott # # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright @@ -19,6 +19,12 @@ srcdir="${0%/*}" cat >ChangeLog <<-EOF This file is generated upon release. Run \`git log\` for a list of changes. EOF + # Force gettextize to be non-interactive. Stupid but necessary. + LC_ALL=C expect -f - <<-EOF + spawn gettextize --force --no-changelog + expect "Press Return to acknowledge the previous * paragraphs." + send "\n" + EOF aclocal autoconf autoheader diff --git a/build-aux/tap-driver.sh b/build-aux/tap-driver.sh new file mode 100644 index 0000000..bc7b1cc --- /dev/null +++ b/build-aux/tap-driver.sh @@ -0,0 +1,651 @@ +#! /bin/sh +# Copyright (C) 2011-2022 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to <bug-automake@gnu.org> or send patches to +# <automake-patches@gnu.org>. + +scriptversion=2013-12-23.17; # UTC + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +me=tap-driver.sh + +fatal () +{ + echo "$me: fatal: $*" >&2 + exit 1 +} + +usage_error () +{ + echo "$me: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat <<END +Usage: + tap-driver.sh --test-name=NAME --log-file=PATH --trs-file=PATH + [--expect-failure={yes|no}] [--color-tests={yes|no}] + [--enable-hard-errors={yes|no}] [--ignore-exit] + [--diagnostic-string=STRING] [--merge|--no-merge] + [--comments|--no-comments] [--] TEST-COMMAND +The '--test-name', '-log-file' and '--trs-file' options are mandatory. +END +} + +# TODO: better error handling in option parsing (in particular, ensure +# TODO: $log_file, $trs_file and $test_name are defined). +test_name= # Used for reporting. +log_file= # Where to save the result and output of the test script. +trs_file= # Where to save the metadata of the test run. +expect_failure=0 +color_tests=0 +merge=0 +ignore_exit=0 +comments=0 +diag_string='#' +while test $# -gt 0; do + case $1 in + --help) print_usage; exit $?;; + --version) echo "$me $scriptversion"; exit $?;; + --test-name) test_name=$2; shift;; + --log-file) log_file=$2; shift;; + --trs-file) trs_file=$2; shift;; + --color-tests) color_tests=$2; shift;; + --expect-failure) expect_failure=$2; shift;; + --enable-hard-errors) shift;; # No-op. + --merge) merge=1;; + --no-merge) merge=0;; + --ignore-exit) ignore_exit=1;; + --comments) comments=1;; + --no-comments) comments=0;; + --diagnostic-string) diag_string=$2; shift;; + --) shift; break;; + -*) usage_error "invalid option: '$1'";; + esac + shift +done + +test $# -gt 0 || usage_error "missing test command" + +case $expect_failure in + yes) expect_failure=1;; + *) expect_failure=0;; +esac + +if test $color_tests = yes; then + init_colors=' + color_map["red"]="[0;31m" # Red. + color_map["grn"]="[0;32m" # Green. + color_map["lgn"]="[1;32m" # Light green. + color_map["blu"]="[1;34m" # Blue. + color_map["mgn"]="[0;35m" # Magenta. + color_map["std"]="[m" # No color. + color_for_result["ERROR"] = "mgn" + color_for_result["PASS"] = "grn" + color_for_result["XPASS"] = "red" + color_for_result["FAIL"] = "red" + color_for_result["XFAIL"] = "lgn" + color_for_result["SKIP"] = "blu"' +else + init_colors='' +fi + +# :; is there to work around a bug in bash 3.2 (and earlier) which +# does not always set '$?' properly on redirection failure. +# See the Autoconf manual for more details. +:;{ + ( + # Ignore common signals (in this subshell only!), to avoid potential + # problems with Korn shells. Some Korn shells are known to propagate + # to themselves signals that have killed a child process they were + # waiting for; this is done at least for SIGINT (and usually only for + # it, in truth). Without the `trap' below, such a behaviour could + # cause a premature exit in the current subshell, e.g., in case the + # test command it runs gets terminated by a SIGINT. Thus, the awk + # script we are piping into would never seen the exit status it + # expects on its last input line (which is displayed below by the + # last `echo $?' statement), and would thus die reporting an internal + # error. + # For more information, see the Autoconf manual and the threads: + # <https://lists.gnu.org/archive/html/bug-autoconf/2011-09/msg00004.html> + # <http://mail.opensolaris.org/pipermail/ksh93-integration-discuss/2009-February/004121.html> + trap : 1 3 2 13 15 + if test $merge -gt 0; then + exec 2>&1 + else + exec 2>&3 + fi + "$@" + echo $? + ) | LC_ALL=C ${AM_TAP_AWK-awk} \ + -v me="$me" \ + -v test_script_name="$test_name" \ + -v log_file="$log_file" \ + -v trs_file="$trs_file" \ + -v expect_failure="$expect_failure" \ + -v merge="$merge" \ + -v ignore_exit="$ignore_exit" \ + -v comments="$comments" \ + -v diag_string="$diag_string" \ +' +# TODO: the usages of "cat >&3" below could be optimized when using +# GNU awk, and/on on systems that supports /dev/fd/. + +# Implementation note: in what follows, `result_obj` will be an +# associative array that (partly) simulates a TAP result object +# from the `TAP::Parser` perl module. + +## ----------- ## +## FUNCTIONS ## +## ----------- ## + +function fatal(msg) +{ + print me ": " msg | "cat >&2" + exit 1 +} + +function abort(where) +{ + fatal("internal error " where) +} + +# Convert a boolean to a "yes"/"no" string. +function yn(bool) +{ + return bool ? "yes" : "no"; +} + +function add_test_result(result) +{ + if (!test_results_index) + test_results_index = 0 + test_results_list[test_results_index] = result + test_results_index += 1 + test_results_seen[result] = 1; +} + +# Whether the test script should be re-run by "make recheck". +function must_recheck() +{ + for (k in test_results_seen) + if (k != "XFAIL" && k != "PASS" && k != "SKIP") + return 1 + return 0 +} + +# Whether the content of the log file associated to this test should +# be copied into the "global" test-suite.log. +function copy_in_global_log() +{ + for (k in test_results_seen) + if (k != "PASS") + return 1 + return 0 +} + +function get_global_test_result() +{ + if ("ERROR" in test_results_seen) + return "ERROR" + if ("FAIL" in test_results_seen || "XPASS" in test_results_seen) + return "FAIL" + all_skipped = 1 + for (k in test_results_seen) + if (k != "SKIP") + all_skipped = 0 + if (all_skipped) + return "SKIP" + return "PASS"; +} + +function stringify_result_obj(result_obj) +{ + if (result_obj["is_unplanned"] || result_obj["number"] != testno) + return "ERROR" + + if (plan_seen == LATE_PLAN) + return "ERROR" + + if (result_obj["directive"] == "TODO") + return result_obj["is_ok"] ? "XPASS" : "XFAIL" + + if (result_obj["directive"] == "SKIP") + return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL; + + if (length(result_obj["directive"])) + abort("in function stringify_result_obj()") + + return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL +} + +function decorate_result(result) +{ + color_name = color_for_result[result] + if (color_name) + return color_map[color_name] "" result "" color_map["std"] + # If we are not using colorized output, or if we do not know how + # to colorize the given result, we should return it unchanged. + return result +} + +function report(result, details) +{ + if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/) + { + msg = ": " test_script_name + add_test_result(result) + } + else if (result == "#") + { + msg = " " test_script_name ":" + } + else + { + abort("in function report()") + } + if (length(details)) + msg = msg " " details + # Output on console might be colorized. + print decorate_result(result) msg + # Log the result in the log file too, to help debugging (this is + # especially true when said result is a TAP error or "Bail out!"). + print result msg | "cat >&3"; +} + +function testsuite_error(error_message) +{ + report("ERROR", "- " error_message) +} + +function handle_tap_result() +{ + details = result_obj["number"]; + if (length(result_obj["description"])) + details = details " " result_obj["description"] + + if (plan_seen == LATE_PLAN) + { + details = details " # AFTER LATE PLAN"; + } + else if (result_obj["is_unplanned"]) + { + details = details " # UNPLANNED"; + } + else if (result_obj["number"] != testno) + { + details = sprintf("%s # OUT-OF-ORDER (expecting %d)", + details, testno); + } + else if (result_obj["directive"]) + { + details = details " # " result_obj["directive"]; + if (length(result_obj["explanation"])) + details = details " " result_obj["explanation"] + } + + report(stringify_result_obj(result_obj), details) +} + +# `skip_reason` should be empty whenever planned > 0. +function handle_tap_plan(planned, skip_reason) +{ + planned += 0 # Avoid getting confused if, say, `planned` is "00" + if (length(skip_reason) && planned > 0) + abort("in function handle_tap_plan()") + if (plan_seen) + { + # Error, only one plan per stream is acceptable. + testsuite_error("multiple test plans") + return; + } + planned_tests = planned + # The TAP plan can come before or after *all* the TAP results; we speak + # respectively of an "early" or a "late" plan. If we see the plan line + # after at least one TAP result has been seen, assume we have a late + # plan; in this case, any further test result seen after the plan will + # be flagged as an error. + plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN) + # If testno > 0, we have an error ("too many tests run") that will be + # automatically dealt with later, so do not worry about it here. If + # $plan_seen is true, we have an error due to a repeated plan, and that + # has already been dealt with above. Otherwise, we have a valid "plan + # with SKIP" specification, and should report it as a particular kind + # of SKIP result. + if (planned == 0 && testno == 0) + { + if (length(skip_reason)) + skip_reason = "- " skip_reason; + report("SKIP", skip_reason); + } +} + +function extract_tap_comment(line) +{ + if (index(line, diag_string) == 1) + { + # Strip leading `diag_string` from `line`. + line = substr(line, length(diag_string) + 1) + # And strip any leading and trailing whitespace left. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + # Return what is left (if any). + return line; + } + return ""; +} + +# When this function is called, we know that line is a TAP result line, +# so that it matches the (perl) RE "^(not )?ok\b". +function setup_result_obj(line) +{ + # Get the result, and remove it from the line. + result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0) + sub("^(not )?ok[ \t]*", "", line) + + # If the result has an explicit number, get it and strip it; otherwise, + # automatically assign the next test number to it. + if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/) + { + match(line, "^[0-9]+") + # The final `+ 0` is to normalize numbers with leading zeros. + result_obj["number"] = substr(line, 1, RLENGTH) + 0 + line = substr(line, RLENGTH + 1) + } + else + { + result_obj["number"] = testno + } + + if (plan_seen == LATE_PLAN) + # No further test results are acceptable after a "late" TAP plan + # has been seen. + result_obj["is_unplanned"] = 1 + else if (plan_seen && testno > planned_tests) + result_obj["is_unplanned"] = 1 + else + result_obj["is_unplanned"] = 0 + + # Strip trailing and leading whitespace. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + + # This will have to be corrected if we have a "TODO"/"SKIP" directive. + result_obj["description"] = line + result_obj["directive"] = "" + result_obj["explanation"] = "" + + if (index(line, "#") == 0) + return # No possible directive, nothing more to do. + + # Directives are case-insensitive. + rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*" + + # See whether we have the directive, and if yes, where. + pos = match(line, rx "$") + if (!pos) + pos = match(line, rx "[^a-zA-Z0-9_]") + + # If there was no TAP directive, we have nothing more to do. + if (!pos) + return + + # Let`s now see if the TAP directive has been escaped. For example: + # escaped: ok \# SKIP + # not escaped: ok \\# SKIP + # escaped: ok \\\\\# SKIP + # not escaped: ok \ # SKIP + if (substr(line, pos, 1) == "#") + { + bslash_count = 0 + for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--) + bslash_count += 1 + if (bslash_count % 2) + return # Directive was escaped. + } + + # Strip the directive and its explanation (if any) from the test + # description. + result_obj["description"] = substr(line, 1, pos - 1) + # Now remove the test description from the line, that has been dealt + # with already. + line = substr(line, pos) + # Strip the directive, and save its value (normalized to upper case). + sub("^[ \t]*#[ \t]*", "", line) + result_obj["directive"] = toupper(substr(line, 1, 4)) + line = substr(line, 5) + # Now get the explanation for the directive (if any), with leading + # and trailing whitespace removed. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + result_obj["explanation"] = line +} + +function get_test_exit_message(status) +{ + if (status == 0) + return "" + if (status !~ /^[1-9][0-9]*$/) + abort("getting exit status") + if (status < 127) + exit_details = "" + else if (status == 127) + exit_details = " (command not found?)" + else if (status >= 128 && status <= 255) + exit_details = sprintf(" (terminated by signal %d?)", status - 128) + else if (status > 256 && status <= 384) + # We used to report an "abnormal termination" here, but some Korn + # shells, when a child process die due to signal number n, can leave + # in $? an exit status of 256+n instead of the more standard 128+n. + # Apparently, both behaviours are allowed by POSIX (2008), so be + # prepared to handle them both. See also Austing Group report ID + # 0000051 <http://www.austingroupbugs.net/view.php?id=51> + exit_details = sprintf(" (terminated by signal %d?)", status - 256) + else + # Never seen in practice. + exit_details = " (abnormal termination)" + return sprintf("exited with status %d%s", status, exit_details) +} + +function write_test_results() +{ + print ":global-test-result: " get_global_test_result() > trs_file + print ":recheck: " yn(must_recheck()) > trs_file + print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file + for (i = 0; i < test_results_index; i += 1) + print ":test-result: " test_results_list[i] > trs_file + close(trs_file); +} + +BEGIN { + +## ------- ## +## SETUP ## +## ------- ## + +'"$init_colors"' + +# Properly initialized once the TAP plan is seen. +planned_tests = 0 + +COOKED_PASS = expect_failure ? "XPASS": "PASS"; +COOKED_FAIL = expect_failure ? "XFAIL": "FAIL"; + +# Enumeration-like constants to remember which kind of plan (if any) +# has been seen. It is important that NO_PLAN evaluates "false" as +# a boolean. +NO_PLAN = 0 +EARLY_PLAN = 1 +LATE_PLAN = 2 + +testno = 0 # Number of test results seen so far. +bailed_out = 0 # Whether a "Bail out!" directive has been seen. + +# Whether the TAP plan has been seen or not, and if yes, which kind +# it is ("early" is seen before any test result, "late" otherwise). +plan_seen = NO_PLAN + +## --------- ## +## PARSING ## +## --------- ## + +is_first_read = 1 + +while (1) + { + # Involutions required so that we are able to read the exit status + # from the last input line. + st = getline + if (st < 0) # I/O error. + fatal("I/O error while reading from input stream") + else if (st == 0) # End-of-input + { + if (is_first_read) + abort("in input loop: only one input line") + break + } + if (is_first_read) + { + is_first_read = 0 + nextline = $0 + continue + } + else + { + curline = nextline + nextline = $0 + $0 = curline + } + # Copy any input line verbatim into the log file. + print | "cat >&3" + # Parsing of TAP input should stop after a "Bail out!" directive. + if (bailed_out) + continue + + # TAP test result. + if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/) + { + testno += 1 + setup_result_obj($0) + handle_tap_result() + } + # TAP plan (normal or "SKIP" without explanation). + else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/) + { + # The next two lines will put the number of planned tests in $0. + sub("^1\\.\\.", "") + sub("[^0-9]*$", "") + handle_tap_plan($0, "") + continue + } + # TAP "SKIP" plan, with an explanation. + else if ($0 ~ /^1\.\.0+[ \t]*#/) + { + # The next lines will put the skip explanation in $0, stripping + # any leading and trailing whitespace. This is a little more + # tricky in truth, since we want to also strip a potential leading + # "SKIP" string from the message. + sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "") + sub("[ \t]*$", ""); + handle_tap_plan(0, $0) + } + # "Bail out!" magic. + # Older versions of prove and TAP::Harness (e.g., 3.17) did not + # recognize a "Bail out!" directive when preceded by leading + # whitespace, but more modern versions (e.g., 3.23) do. So we + # emulate the latter, "more modern" behaviour. + else if ($0 ~ /^[ \t]*Bail out!/) + { + bailed_out = 1 + # Get the bailout message (if any), with leading and trailing + # whitespace stripped. The message remains stored in `$0`. + sub("^[ \t]*Bail out![ \t]*", ""); + sub("[ \t]*$", ""); + # Format the error message for the + bailout_message = "Bail out!" + if (length($0)) + bailout_message = bailout_message " " $0 + testsuite_error(bailout_message) + } + # Maybe we have too look for dianogtic comments too. + else if (comments != 0) + { + comment = extract_tap_comment($0); + if (length(comment)) + report("#", comment); + } + } + +## -------- ## +## FINISH ## +## -------- ## + +# A "Bail out!" directive should cause us to ignore any following TAP +# error, as well as a non-zero exit status from the TAP producer. +if (!bailed_out) + { + if (!plan_seen) + { + testsuite_error("missing test plan") + } + else if (planned_tests != testno) + { + bad_amount = testno > planned_tests ? "many" : "few" + testsuite_error(sprintf("too %s tests run (expected %d, got %d)", + bad_amount, planned_tests, testno)) + } + if (!ignore_exit) + { + # Fetch exit status from the last line. + exit_message = get_test_exit_message(nextline) + if (exit_message) + testsuite_error(exit_message) + } + } + +write_test_results() + +exit 0 + +} # End of "BEGIN" block. +' + +# TODO: document that we consume the file descriptor 3 :-( +} 3>"$log_file" + +test $? -eq 0 || fatal "I/O or internal error" + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/configure.ac b/configure.ac index 3ff9f79..5b527de 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # Process this file with autoconf to produce a configure script. # -# Copyright (C) 2013, 2017, 2019, 2021 P. J. McDermott +# Copyright (C) 2013, 2017, 2019, 2021, 2022 P. J. McDermott # # This file is part of @ # @@ -30,6 +30,14 @@ AM_CONDITIONAL([IN_GIT], AM_CONDITIONAL([OUT_OF_TREE], [test x"${srcdir}" != x'.' ]) +AM_CONDITIONAL([CROSS_COMPILING], [! test x"${build_alias}" = x"${host_alias}"]) + +GETTEXT_PACKAGE="${PACKAGE}" +AC_SUBST([GETTEXT_PACKAGE]) +AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["${GETTEXT_PACKAGE}"], + [Define the gettext package to be used.]) +AM_GNU_GETTEXT([external]) + save_CFLAGS="${CFLAGS-}" AC_PROG_CC() AM_PROG_CC_C_O() @@ -38,8 +46,9 @@ test -d "${srcdir}/.git" || CFLAGS="${save_CFLAGS}" funcs_missing=false AC_CHECK_FUNCS( [\ - calloc fprintf fputs free isspace localtime mktime printf \ - realloc setvbuf sleep strerror strlen strftime strptime time + calloc difftime fprintf fputs free isspace localtime mktime \ + printf realloc setvbuf sleep strerror strlen strftime strptime \ + time ], [], [funcs_missing=true]) @@ -53,6 +62,40 @@ if ${funcs_missing}; then AC_MSG_ERROR([required functions are missing]) fi +AC_PROG_LN_S() +AC_ARG_ENABLE([symlink], + [AS_HELP_STRING([--disable-symlink], + [don't install \`@' symbolic link])], + [case "${enableval}" in yes|no) symlink=${enableval};; + *) AC_MSG_ERROR( + [bad value ${enableval} for symlink option]);; + esac], + [symlink=yes] +) +AM_CONDITIONAL([ENABLE_SYMLINK], [test x"${symlink}" = x'yes']) + +AC_ARG_ENABLE([tests], + [AS_HELP_STRING([--disable-tests], + [exclude test suite support from executable])], + [case "${enableval}" in yes|no) tests=${enableval};; + *) AC_MSG_ERROR( + [bad value ${enableval} for tests option]);; + esac], + [tests=yes] +) +if test x"${tests}" = x'yes'; then + AC_DEFINE([ENABLE_TESTS], [1], + [Define to 1 to include test suite support]) + AC_CHECK_FUNCS( + [sscanf], + [], + [AC_MSG_ERROR([required functions are missing])]) +else + AC_DEFINE([ENABLE_TESTS], [0], + [Define to 1 to include test suite support]) +fi +AM_CONDITIONAL([ENABLE_TESTS], [test x"${tests}" = x'yes']) + AX_CFLAGS_WARN_ALL() dnl Adds -Wall or equivalent AX_CHECK_COMPILE_FLAG([-Wpedantic], [AX_APPEND_FLAG([-Wpedantic])]) AX_CHECK_COMPILE_FLAG([-Wextra], [AX_APPEND_FLAG([-Wextra])]) @@ -114,6 +157,6 @@ for flag in -Os -fno-unwind-tables -fno-asynchronous-unwind-tables \ AX_CHECK_COMPILE_FLAG([${flag}], [AX_APPEND_FLAG([${flag}])]) done -AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([Makefile po/Makefile.in]) AC_CONFIG_HEADERS([config.h]) AC_OUTPUT() diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 0000000..a9c97ca --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,10 @@ +/*.m4 +!/ax_append_flag.m4 +!/ax_cflags_warn_all.m4 +!/ax_check_compile_flag.m4 +!/ax_compiler_vendor.m4 +!/ax_prepend_flag.m4 +!/ax_require_defined.m4 +!/gcc_version.m4 +!/gl_manywarnings.m4 +!/gl_warnings.m4 diff --git a/m4/ax_append_flag.m4 b/m4/ax_append_flag.m4 index e8c5312..dd6d8b6 100644 --- a/m4/ax_append_flag.m4 +++ b/m4/ax_append_flag.m4 @@ -23,33 +23,12 @@ # Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> # Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> # -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <https://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. -#serial 7 +#serial 8 AC_DEFUN([AX_APPEND_FLAG], [dnl diff --git a/m4/ax_cflags_warn_all.m4 b/m4/ax_cflags_warn_all.m4 index 094577e..9235a18 100644 --- a/m4/ax_cflags_warn_all.m4 +++ b/m4/ax_cflags_warn_all.m4 @@ -4,33 +4,54 @@ # # SYNOPSIS # -# AX_CFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])] -# AX_CXXFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])] -# AX_FCFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])] +# AX_CFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])] +# AX_CXXFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])] +# AX_FCFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])] # # DESCRIPTION # -# Try to find a compiler option that enables most reasonable warnings. +# Specify compiler options that enable most reasonable warnings. For the +# GNU Compiler Collection (GCC), for example, it will be "-Wall". The +# result is added to shellvar, one of CFLAGS, CXXFLAGS or FCFLAGS if the +# first parameter is not specified. # -# For the GNU compiler it will be -Wall (and -ansi -pedantic) The result -# is added to the shellvar being CFLAGS, CXXFLAGS, or FCFLAGS by default. +# Each of these macros accepts the following optional arguments: # -# Currently this macro knows about the GCC, Solaris, Digital Unix, AIX, -# HP-UX, IRIX, NEC SX-5 (Super-UX 10), Cray J90 (Unicos 10.0.0.8), and -# Intel compilers. For a given compiler, the Fortran flags are much more -# experimental than their C equivalents. +# - $1 - shellvar +# shell variable to use (CFLAGS, CXXFLAGS or FCFLAGS if not +# specified, depending on macro) # -# - $1 shell-variable-to-add-to : CFLAGS, CXXFLAGS, or FCFLAGS -# - $2 add-value-if-not-found : nothing -# - $3 action-if-found : add value to shellvariable -# - $4 action-if-not-found : nothing +# - $2 - default +# value to use for flags if compiler vendor cannot be determined (by +# default, "") # -# NOTE: These macros depend on AX_APPEND_FLAG. +# - $3 - action-if-found +# action to take if the compiler vendor has been successfully +# determined (by default, add the appropriate compiler flags to +# shellvar) +# +# - $4 - action-if-not-found +# action to take if the compiler vendor has not been determined or +# is unknown (by default, add the default flags, or "" if not +# specified, to shellvar) +# +# These macros use AX_COMPILER_VENDOR to determine which flags should be +# returned for a given compiler. Not all compilers currently have flags +# defined for them; patches are welcome. If need be, compiler flags may +# be made language-dependent: use a construct like the following: +# +# [vendor_name], [m4_if(_AC_LANG_PREFIX,[C], VAR="--relevant-c-flags",dnl +# m4_if(_AC_LANG_PREFIX,[CXX], VAR="--relevant-c++-flags",dnl +# m4_if(_AC_LANG_PREFIX,[FC], VAR="--relevant-fortran-flags",dnl +# VAR="$2"; FOUND="no")))], +# +# Note: These macros also depend on AX_PREPEND_FLAG. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> # Copyright (c) 2010 Rhys Ulerich <rhys.ulerich@gmail.com> +# Copyright (c) 2018 John Zaitseff <J.Zaitseff@zap.org.au> # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the @@ -58,65 +79,80 @@ # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. -#serial 16 +#serial 25 + +AC_DEFUN([AX_FLAGS_WARN_ALL], [ + AX_REQUIRE_DEFINED([AX_PREPEND_FLAG])dnl + AC_REQUIRE([AX_COMPILER_VENDOR])dnl + + AS_VAR_PUSHDEF([FLAGS], [m4_default($1,_AC_LANG_PREFIX[]FLAGS)])dnl + AS_VAR_PUSHDEF([VAR], [ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl + AS_VAR_PUSHDEF([FOUND], [ac_save_[]_AC_LANG_ABBREV[]flags_warn_all_found])dnl + + AC_CACHE_CHECK([FLAGS for most reasonable warnings], VAR, [ + VAR="" + FOUND="yes" + dnl Cases are listed in the order found in ax_compiler_vendor.m4 + AS_CASE("$ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor", + [intel], [VAR="-w2"], + [ibm], [VAR="-qsrcmsg -qinfo=all:noppt:noppc:noobs:nocnd"], + [pathscale], [], + [clang], [VAR="-Wall"], + [cray], [VAR="-h msglevel 2"], + [fujitsu], [], + [sdcc], [], + [sx], [VAR="-pvctl[,]fullmsg"], + [portland], [], + [gnu], [VAR="-Wall"], + [sun], [VAR="-v"], + [hp], [VAR="+w1"], + [dec], [VAR="-verbose -w0 -warnprotos"], + [borland], [], + [comeau], [], + [kai], [], + [lcc], [], + [sgi], [VAR="-fullwarn"], + [microsoft], [], + [metrowerks], [], + [watcom], [], + [tcc], [], + [unknown], [ + VAR="$2" + FOUND="no" + ], + [ + AC_MSG_WARN([Unknown compiler vendor returned by [AX_COMPILER_VENDOR]]) + VAR="$2" + FOUND="no" + ] + ) + + AS_IF([test "x$FOUND" = "xyes"], [dnl + m4_default($3, [AS_IF([test "x$VAR" != "x"], [AX_PREPEND_FLAG([$VAR], [FLAGS])])]) + ], [dnl + m4_default($4, [m4_ifval($2, [AX_PREPEND_FLAG([$VAR], [FLAGS])], [true])]) + ])dnl + ])dnl -AC_DEFUN([AX_FLAGS_WARN_ALL],[dnl -AS_VAR_PUSHDEF([FLAGS],[_AC_LANG_PREFIX[]FLAGS])dnl -AS_VAR_PUSHDEF([VAR],[ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl -AC_CACHE_CHECK([m4_ifval($1,$1,FLAGS) for maximum warnings], -VAR,[VAR="no, unknown" -ac_save_[]FLAGS="$[]FLAGS" -for ac_arg dnl -in "-warn all % -warn all" dnl Intel - "-pedantic % -Wall" dnl GCC - "-xstrconst % -v" dnl Solaris C - "-std1 % -verbose -w0 -warnprotos" dnl Digital Unix - "-qlanglvl=ansi % -qsrcmsg -qinfo=all:noppt:noppc:noobs:nocnd" dnl AIX - "-ansi -ansiE % -fullwarn" dnl IRIX - "+ESlit % +w1" dnl HP-UX C - "-Xc % -pvctl[,]fullmsg" dnl NEC SX-5 (Super-UX 10) - "-h conform % -h msglevel 2" dnl Cray C (Unicos) - # -do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'` - AC_COMPILE_IFELSE([AC_LANG_PROGRAM], - [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break]) -done -FLAGS="$ac_save_[]FLAGS" -]) -AS_VAR_POPDEF([FLAGS])dnl -AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) -case ".$VAR" in - .ok|.ok,*) m4_ifvaln($3,$3) ;; - .|.no|.no,*) m4_default($4,[m4_ifval($2,[AX_APPEND_FLAG([$2], [$1])])]) ;; - *) m4_default($3,[AX_APPEND_FLAG([$VAR], [$1])]) ;; -esac -AS_VAR_POPDEF([VAR])dnl + AS_VAR_POPDEF([FOUND])dnl + AS_VAR_POPDEF([VAR])dnl + AS_VAR_POPDEF([FLAGS])dnl ])dnl AX_FLAGS_WARN_ALL -dnl implementation tactics: -dnl the for-argument contains a list of options. The first part of -dnl these does only exist to detect the compiler - usually it is -dnl a global option to enable -ansi or -extrawarnings. All other -dnl compilers will fail about it. That was needed since a lot of -dnl compilers will give false positives for some option-syntax -dnl like -Woption or -Xoption as they think of it is a pass-through -dnl to later compile stages or something. The "%" is used as a -dnl delimiter. A non-option comment can be given after "%%" marks -dnl which will be shown but not added to the respective C/CXXFLAGS. -AC_DEFUN([AX_CFLAGS_WARN_ALL],[dnl -AC_LANG_PUSH([C]) -AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) -AC_LANG_POP([C]) -]) +AC_DEFUN([AX_CFLAGS_WARN_ALL], [dnl + AC_LANG_PUSH([C]) + AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) + AC_LANG_POP([C]) +])dnl -AC_DEFUN([AX_CXXFLAGS_WARN_ALL],[dnl -AC_LANG_PUSH([C++]) -AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) -AC_LANG_POP([C++]) -]) +AC_DEFUN([AX_CXXFLAGS_WARN_ALL], [dnl + AC_LANG_PUSH([C++]) + AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) + AC_LANG_POP([C++]) +])dnl -AC_DEFUN([AX_FCFLAGS_WARN_ALL],[dnl -AC_LANG_PUSH([Fortran]) -AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) -AC_LANG_POP([Fortran]) -]) +AC_DEFUN([AX_FCFLAGS_WARN_ALL], [dnl + AC_LANG_PUSH([Fortran]) + AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) + AC_LANG_POP([Fortran]) +])dnl diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 index dcabb92..bd753b3 100644 --- a/m4/ax_check_compile_flag.m4 +++ b/m4/ax_check_compile_flag.m4 @@ -29,33 +29,12 @@ # Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> # Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> # -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <https://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. -#serial 5 +#serial 6 AC_DEFUN([AX_CHECK_COMPILE_FLAG], [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF diff --git a/m4/ax_compiler_vendor.m4 b/m4/ax_compiler_vendor.m4 new file mode 100644 index 0000000..039f99d --- /dev/null +++ b/m4/ax_compiler_vendor.m4 @@ -0,0 +1,119 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_compiler_vendor.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_COMPILER_VENDOR +# +# DESCRIPTION +# +# Determine the vendor of the C, C++ or Fortran compiler. The vendor is +# returned in the cache variable $ax_cv_c_compiler_vendor for C, +# $ax_cv_cxx_compiler_vendor for C++ or $ax_cv_fc_compiler_vendor for +# (modern) Fortran. The value is one of "intel", "ibm", "pathscale", +# "clang" (LLVM), "cray", "fujitsu", "sdcc", "sx", "nvhpc" (NVIDIA HPC +# Compiler), "portland" (PGI), "gnu" (GCC), "sun" (Oracle Developer +# Studio), "hp", "dec", "borland", "comeau", "kai", "lcc", "sgi", +# "microsoft", "metrowerks", "watcom", "tcc" (Tiny CC) or "unknown" (if +# the compiler cannot be determined). +# +# To check for a Fortran compiler, you must first call AC_FC_PP_SRCEXT +# with an appropriate preprocessor-enabled extension. For example: +# +# AC_LANG_PUSH([Fortran]) +# AC_PROG_FC +# AC_FC_PP_SRCEXT([F]) +# AX_COMPILER_VENDOR +# AC_LANG_POP([Fortran]) +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu> +# Copyright (c) 2008 Matteo Frigo +# Copyright (c) 2018-19 John Zaitseff <J.Zaitseff@zap.org.au> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <https://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 32 + +AC_DEFUN([AX_COMPILER_VENDOR], [dnl + AC_CACHE_CHECK([for _AC_LANG compiler vendor], ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor, [dnl + dnl If you modify this list of vendors, please add similar support + dnl to ax_compiler_version.m4 if at all possible. + dnl + dnl Note: Do NOT check for GCC first since some other compilers + dnl define __GNUC__ to remain compatible with it. Compilers that + dnl are very slow to start (such as Intel) are listed first. + + vendors=" + intel: __ICC,__ECC,__INTEL_COMPILER + ibm: __xlc__,__xlC__,__IBMC__,__IBMCPP__,__ibmxl__ + pathscale: __PATHCC__,__PATHSCALE__ + clang: __clang__ + cray: _CRAYC + fujitsu: __FUJITSU + sdcc: SDCC,__SDCC + sx: _SX + nvhpc: __NVCOMPILER + portland: __PGI + gnu: __GNUC__ + sun: __SUNPRO_C,__SUNPRO_CC,__SUNPRO_F90,__SUNPRO_F95 + hp: __HP_cc,__HP_aCC + dec: __DECC,__DECCXX,__DECC_VER,__DECCXX_VER + borland: __BORLANDC__,__CODEGEARC__,__TURBOC__ + comeau: __COMO__ + kai: __KCC + lcc: __LCC__ + sgi: __sgi,sgi + microsoft: _MSC_VER + metrowerks: __MWERKS__ + watcom: __WATCOMC__ + tcc: __TINYC__ + unknown: UNKNOWN + " + for ventest in $vendors; do + case $ventest in + *:) + vendor=$ventest + continue + ;; + *) + vencpp="defined("`echo $ventest | sed 's/,/) || defined(/g'`")" + ;; + esac + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [[ +#if !($vencpp) + thisisanerror; +#endif + ]])], [break]) + done + + ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor=`echo $vendor | cut -d: -f1` + ]) +])dnl diff --git a/m4/ax_prepend_flag.m4 b/m4/ax_prepend_flag.m4 new file mode 100644 index 0000000..adac8c5 --- /dev/null +++ b/m4/ax_prepend_flag.m4 @@ -0,0 +1,51 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prepend_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PREPEND_FLAG(FLAG, [FLAGS-VARIABLE]) +# +# DESCRIPTION +# +# FLAG is added to the front of the FLAGS-VARIABLE shell variable, with a +# space added in between. +# +# If FLAGS-VARIABLE is not specified, the current language's flags (e.g. +# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains +# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly +# FLAG. +# +# NOTE: Implementation based on AX_APPEND_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# Copyright (c) 2018 John Zaitseff <J.Zaitseff@zap.org.au> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 2 + +AC_DEFUN([AX_PREPEND_FLAG], +[dnl +AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF +AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])]) +AS_VAR_SET_IF(FLAGS,[ + AS_CASE([" AS_VAR_GET(FLAGS) "], + [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])], + [ + FLAGS="$1 $FLAGS" + AC_RUN_LOG([: FLAGS="$FLAGS"]) + ]) + ], + [ + AS_VAR_SET(FLAGS,[$1]) + AC_RUN_LOG([: FLAGS="$FLAGS"]) + ]) +AS_VAR_POPDEF([FLAGS])dnl +])dnl AX_PREPEND_FLAG diff --git a/m4/gcc_version.m4 b/m4/gcc_version.m4 index 9e16450..703973c 100644 --- a/m4/gcc_version.m4 +++ b/m4/gcc_version.m4 @@ -1,22 +1,18 @@ -dnl Copied from GNU Coreutils configure.ac -dnl -dnl The 17 lines are probably too trivial to be copyrightable, but just in case: -dnl -dnl Copyright (C) 1991-2017 Free Software Foundation, Inc. -dnl -dnl This program is free software: you can redistribute it and/or modify -dnl it under the terms of the GNU General Public License as published by -dnl the Free Software Foundation, either version 3 of the License, or -dnl (at your option) any later version. -dnl -dnl This program is distributed in the hope that it will be useful, -dnl but WITHOUT ANY WARRANTY; without even the implied warranty of -dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -dnl GNU General Public License for more details. -dnl -dnl You should have received a copy of the GNU General Public License -dnl along with this program. If not, see <https://www.gnu.org/licenses/>. -dnl +# Copyright (C) 1991-2023 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + dnl Written by Jim Meyering. # gl_GCC_VERSION_IFELSE([major], [minor], [run-if-found], [run-if-not-found]) diff --git a/m4/gl_manywarnings.m4 b/m4/gl_manywarnings.m4 index d10bcd0..a06f26f 100644 --- a/m4/gl_manywarnings.m4 +++ b/m4/gl_manywarnings.m4 @@ -1,11 +1,13 @@ -# manywarnings.m4 serial 13 -dnl Copyright (C) 2008-2017 Free Software Foundation, Inc. +# manywarnings.m4 serial 24 +dnl Copyright (C) 2008-2023 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Simon Josefsson +AC_PREREQ([2.64]) + # gl_MANYWARN_COMPLEMENT(OUTVAR, LISTVAR, REMOVEVAR) # -------------------------------------------------- # Copy LISTVAR to OUTVAR except for the entries in REMOVEVAR. @@ -21,7 +23,7 @@ AC_DEFUN([gl_MANYWARN_COMPLEMENT], *" $gl_warn_item "*) ;; *) - gl_warn_set="$gl_warn_set $gl_warn_item" + AS_VAR_APPEND([gl_warn_set], [" $gl_warn_item"]) ;; esac done @@ -39,291 +41,166 @@ AC_DEFUN([gl_MANYWARN_ALL_GCC], [_AC_LANG_DISPATCH([$0], _AC_LANG, $@)]) # Specialization for _AC_LANG = C. -# Use of m4_defun rather than AC_DEFUN works around a bug in autoconf < 2.63b. -m4_defun([gl_MANYWARN_ALL_GCC(C)], +AC_DEFUN([gl_MANYWARN_ALL_GCC(C)], [ AC_LANG_PUSH([C]) dnl First, check for some issues that only occur when combining multiple dnl gcc warning categories. AC_REQUIRE([AC_PROG_CC]) - if test -n "$GCC"; then - - dnl Check if -W -Werror -Wno-missing-field-initializers is supported - dnl with the current $CC $CFLAGS $CPPFLAGS. - AC_MSG_CHECKING([whether -Wno-missing-field-initializers is supported]) - AC_CACHE_VAL([gl_cv_cc_nomfi_supported], [ - gl_save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS -W -Werror -Wno-missing-field-initializers" - AC_COMPILE_IFELSE( - [AC_LANG_PROGRAM([[]], [[]])], - [gl_cv_cc_nomfi_supported=yes], - [gl_cv_cc_nomfi_supported=no]) - CFLAGS="$gl_save_CFLAGS"]) - AC_MSG_RESULT([$gl_cv_cc_nomfi_supported]) - - if test "$gl_cv_cc_nomfi_supported" = yes; then - dnl Now check whether -Wno-missing-field-initializers is needed - dnl for the { 0, } construct. - AC_MSG_CHECKING([whether -Wno-missing-field-initializers is needed]) - AC_CACHE_VAL([gl_cv_cc_nomfi_needed], [ - gl_save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS -W -Werror" - AC_COMPILE_IFELSE( - [AC_LANG_PROGRAM( - [[int f (void) - { - typedef struct { int a; int b; } s_t; - s_t s1 = { 0, }; - return s1.b; - } - ]], - [[]])], - [gl_cv_cc_nomfi_needed=no], - [gl_cv_cc_nomfi_needed=yes]) - CFLAGS="$gl_save_CFLAGS" - ]) - AC_MSG_RESULT([$gl_cv_cc_nomfi_needed]) - fi + AS_IF([test -n "$GCC"], [ + AC_CACHE_CHECK([whether -Wno-missing-field-initializers is needed], + [gl_cv_cc_nomfi_needed], + [gl_cv_cc_nomfi_needed=no + gl_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -Wextra -Werror" + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[struct file_data { int desc, name; }; + struct cmp { struct file_data file[1]; }; + void f (struct cmp *r) + { + typedef struct { int a; int b; } s_t; + s_t s1 = { 0, }; + struct cmp cmp = { .file[0].desc = r->file[0].desc + s1.a }; + *r = cmp; + } + ]], + [[]])], + [], + [CFLAGS="$CFLAGS -Wno-missing-field-initializers" + AC_COMPILE_IFELSE([], + [gl_cv_cc_nomfi_needed=yes])]) + CFLAGS="$gl_save_CFLAGS" + ]) dnl Next, check if -Werror -Wuninitialized is useful with the dnl user's choice of $CFLAGS; some versions of gcc warn that it dnl has no effect if -O is not also used - AC_MSG_CHECKING([whether -Wuninitialized is supported]) - AC_CACHE_VAL([gl_cv_cc_uninitialized_supported], [ - gl_save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS -Werror -Wuninitialized" - AC_COMPILE_IFELSE( - [AC_LANG_PROGRAM([[]], [[]])], - [gl_cv_cc_uninitialized_supported=yes], - [gl_cv_cc_uninitialized_supported=no]) - CFLAGS="$gl_save_CFLAGS"]) - AC_MSG_RESULT([$gl_cv_cc_uninitialized_supported]) - - fi + AC_CACHE_CHECK([whether -Wuninitialized is supported], + [gl_cv_cc_uninitialized_supported], + [gl_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -Werror -Wuninitialized" + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[]], [[]])], + [gl_cv_cc_uninitialized_supported=yes], + [gl_cv_cc_uninitialized_supported=no]) + CFLAGS="$gl_save_CFLAGS" + ]) + ]) # List all gcc warning categories. # To compare this list to your installed GCC's, run this Bash command: # # comm -3 \ - # <(sed -n 's/^ *\(-[^ ]*\) .*/\1/p' manywarnings.m4 | sort) \ - # <(gcc --help=warnings | sed -n 's/^ \(-[^ ]*\) .*/\1/p' | sort | - # grep -v -x -F -f <( - # awk '/^[^#]/ {print $1}' ../build-aux/gcc-warning.spec)) + # <((sed -n 's/^ *\(-[^ 0-9][^ ]*\).*/\1/p' manywarnings.m4; \ + # awk '/^[^#]/ {print $1}' ../build-aux/gcc-warning.spec) | sort) \ + # <(LC_ALL=C gcc --help=warnings | sed -n 's/^ \(-[^ ]*\) .*/\1/p' | sort) - gl_manywarn_set= - for gl_manywarn_item in -fno-common \ - -W \ - -Wabi \ - -Waddress \ - -Waggressive-loop-optimizations \ + $1= + for gl_manywarn_item in -fanalyzer -fstrict-flex-arrays \ -Wall \ - -Wattributes \ + -Warith-conversion \ -Wbad-function-cast \ - -Wbool-compare \ - -Wbool-operation \ - -Wbuiltin-declaration-mismatch \ - -Wbuiltin-macro-redefined \ - -Wcast-align \ - -Wchar-subscripts \ - -Wchkp \ - -Wclobbered \ - -Wcomment \ - -Wcomments \ - -Wcoverage-mismatch \ - -Wcpp \ - -Wdangling-else \ + -Wcast-align=strict \ -Wdate-time \ - -Wdeprecated \ - -Wdeprecated-declarations \ - -Wdesignated-init \ -Wdisabled-optimization \ - -Wdiscarded-array-qualifiers \ - -Wdiscarded-qualifiers \ - -Wdiv-by-zero \ -Wdouble-promotion \ -Wduplicated-branches \ -Wduplicated-cond \ - -Wduplicate-decl-specifier \ - -Wempty-body \ - -Wendif-labels \ - -Wenum-compare \ - -Wexpansion-to-defined \ -Wextra \ - -Wformat-contains-nul \ - -Wformat-extra-args \ - -Wformat-nonliteral \ - -Wformat-security \ -Wformat-signedness \ - -Wformat-y2k \ - -Wformat-zero-length \ - -Wframe-address \ - -Wfree-nonheap-object \ - -Whsa \ - -Wignored-attributes \ - -Wignored-qualifiers \ - -Wimplicit \ - -Wimplicit-function-declaration \ - -Wimplicit-int \ - -Wincompatible-pointer-types \ -Winit-self \ -Winline \ - -Wint-conversion \ - -Wint-in-bool-context \ - -Wint-to-pointer-cast \ - -Winvalid-memory-model \ -Winvalid-pch \ - -Wjump-misses-init \ - -Wlogical-not-parentheses \ -Wlogical-op \ - -Wmain \ - -Wmaybe-uninitialized \ - -Wmemset-elt-size \ - -Wmemset-transposed-args \ - -Wmisleading-indentation \ - -Wmissing-braces \ -Wmissing-declarations \ - -Wmissing-field-initializers \ -Wmissing-include-dirs \ - -Wmissing-parameter-type \ -Wmissing-prototypes \ - -Wmultichar \ - -Wnarrowing \ -Wnested-externs \ - -Wnonnull \ - -Wnonnull-compare \ -Wnull-dereference \ - -Wodr \ - -Wold-style-declaration \ -Wold-style-definition \ -Wopenmp-simd \ - -Woverflow \ -Woverlength-strings \ - -Woverride-init \ -Wpacked \ - -Wpacked-bitfield-compat \ - -Wparentheses \ -Wpointer-arith \ - -Wpointer-compare \ - -Wpointer-sign \ - -Wpointer-to-int-cast \ - -Wpragmas \ - -Wpsabi \ - -Wrestrict \ - -Wreturn-local-addr \ - -Wreturn-type \ - -Wscalar-storage-order \ - -Wsequence-point \ -Wshadow \ - -Wshift-count-negative \ - -Wshift-count-overflow \ - -Wshift-negative-value \ - -Wsizeof-array-argument \ - -Wsizeof-pointer-memaccess \ -Wstack-protector \ - -Wstrict-aliasing \ + -Wstrict-flex-arrays \ -Wstrict-overflow \ -Wstrict-prototypes \ + -Wsuggest-attribute=cold \ -Wsuggest-attribute=const \ -Wsuggest-attribute=format \ + -Wsuggest-attribute=malloc \ -Wsuggest-attribute=noreturn \ -Wsuggest-attribute=pure \ -Wsuggest-final-methods \ -Wsuggest-final-types \ - -Wswitch \ - -Wswitch-bool \ - -Wswitch-default \ - -Wswitch-unreachable \ -Wsync-nand \ -Wsystem-headers \ - -Wtautological-compare \ -Wtrampolines \ - -Wtrigraphs \ - -Wtype-limits \ -Wuninitialized \ -Wunknown-pragmas \ -Wunsafe-loop-optimizations \ - -Wunused \ - -Wunused-but-set-parameter \ - -Wunused-but-set-variable \ - -Wunused-function \ - -Wunused-label \ - -Wunused-local-typedefs \ -Wunused-macros \ - -Wunused-parameter \ - -Wunused-result \ - -Wunused-value \ - -Wunused-variable \ - -Wvarargs \ -Wvariadic-macros \ -Wvector-operation-performance \ -Wvla \ - -Wvolatile-register-var \ -Wwrite-strings \ \ ; do - gl_manywarn_set="$gl_manywarn_set $gl_manywarn_item" + AS_VAR_APPEND([$1], [" $gl_manywarn_item"]) done # gcc --help=warnings outputs an unusual form for these options; list # them here so that the above 'comm' command doesn't report a false match. - # Would prefer "min (PTRDIFF_MAX, SIZE_MAX)", but it must be a literal. - # Also, AC_COMPUTE_INT requires it to fit in a long; it is 2**63 on - # the only platforms where it does not fit in a long, so make that - # a special case. - AC_MSG_CHECKING([max safe object size]) - AC_COMPUTE_INT([gl_alloc_max], - [LONG_MAX < (PTRDIFF_MAX < (size_t) -1 ? PTRDIFF_MAX : (size_t) -1) - ? -1 - : PTRDIFF_MAX < (size_t) -1 ? (long) PTRDIFF_MAX : (long) (size_t) -1], - [[#include <limits.h> - #include <stddef.h> - #include <stdint.h> - ]], - [gl_alloc_max=2147483647]) - case $gl_alloc_max in - -1) gl_alloc_max=9223372036854775807;; - esac - AC_MSG_RESULT([$gl_alloc_max]) - gl_manywarn_set="$gl_manywarn_set -Walloc-size-larger-than=$gl_alloc_max" - gl_manywarn_set="$gl_manywarn_set -Warray-bounds=2" - gl_manywarn_set="$gl_manywarn_set -Wformat-overflow=2" - gl_manywarn_set="$gl_manywarn_set -Wformat-truncation=2" - gl_manywarn_set="$gl_manywarn_set -Wimplicit-fallthrough=5" - gl_manywarn_set="$gl_manywarn_set -Wnormalized=nfc" - gl_manywarn_set="$gl_manywarn_set -Wshift-overflow=2" - gl_manywarn_set="$gl_manywarn_set -Wstringop-overflow=2" - gl_manywarn_set="$gl_manywarn_set -Wunused-const-variable=2" - gl_manywarn_set="$gl_manywarn_set -Wvla-larger-than=4031" + AS_VAR_APPEND([$1], [' -Warray-bounds=2']) + AS_VAR_APPEND([$1], [' -Wattribute-alias=2']) + AS_VAR_APPEND([$1], [' -Wbidi-chars=any,ucn']) + AS_VAR_APPEND([$1], [' -Wformat-overflow=2']) + AS_VAR_APPEND([$1], [' -Wformat=2']) + AS_VAR_APPEND([$1], [' -Wformat-truncation=2']) + AS_VAR_APPEND([$1], [' -Wimplicit-fallthrough=5']) + AS_VAR_APPEND([$1], [' -Wshift-overflow=2']) + AS_VAR_APPEND([$1], [' -Wuse-after-free=3']) + AS_VAR_APPEND([$1], [' -Wunused-const-variable=2']) + AS_VAR_APPEND([$1], [' -Wvla-larger-than=4031']) # These are needed for older GCC versions. - if test -n "$GCC"; then - case `($CC --version) 2>/dev/null` in + if test -n "$GCC" && gl_gcc_version=`($CC --version) 2>/dev/null`; then + case $gl_gcc_version in 'gcc (GCC) '[[0-3]].* | \ 'gcc (GCC) '4.[[0-7]].*) - gl_manywarn_set="$gl_manywarn_set -fdiagnostics-show-option" - gl_manywarn_set="$gl_manywarn_set -funit-at-a-time" + AS_VAR_APPEND([$1], [' -fdiagnostics-show-option']) + AS_VAR_APPEND([$1], [' -funit-at-a-time']) + ;; + esac + case $gl_gcc_version in + 'gcc (GCC) '[[0-9]].*) + AS_VAR_APPEND([$1], [' -fno-common']) ;; esac fi # Disable specific options as needed. if test "$gl_cv_cc_nomfi_needed" = yes; then - gl_manywarn_set="$gl_manywarn_set -Wno-missing-field-initializers" + AS_VAR_APPEND([$1], [' -Wno-missing-field-initializers']) fi if test "$gl_cv_cc_uninitialized_supported" = no; then - gl_manywarn_set="$gl_manywarn_set -Wno-uninitialized" + AS_VAR_APPEND([$1], [' -Wno-uninitialized']) fi - $1=$gl_manywarn_set + # This warning have too many false alarms in GCC 11.2.1. + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101713 + AS_VAR_APPEND([$1], [' -Wno-analyzer-malloc-leak']) AC_LANG_POP([C]) ]) # Specialization for _AC_LANG = C++. -# Use of m4_defun rather than AC_DEFUN works around a bug in autoconf < 2.63b. -m4_defun([gl_MANYWARN_ALL_GCC(C++)], +AC_DEFUN([gl_MANYWARN_ALL_GCC(C++)], [ gl_MANYWARN_ALL_GCC_CXX_IMPL([$1]) ]) diff --git a/m4/gl_warnings.m4 b/m4/gl_warnings.m4 index 870472b..9433cb5 100644 --- a/m4/gl_warnings.m4 +++ b/m4/gl_warnings.m4 @@ -1,19 +1,12 @@ -# warnings.m4 serial 13 -dnl Copyright (C) 2008-2017 Free Software Foundation, Inc. +# warnings.m4 serial 19 +dnl Copyright (C) 2008-2023 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Simon Josefsson -# gl_AS_VAR_APPEND(VAR, VALUE) -# ---------------------------- -# Provide the functionality of AS_VAR_APPEND if Autoconf does not have it. -m4_ifdef([AS_VAR_APPEND], -[m4_copy([AS_VAR_APPEND], [gl_AS_VAR_APPEND])], -[m4_define([gl_AS_VAR_APPEND], -[AS_VAR_SET([$1], [AS_VAR_GET([$1])$2])])]) - +AC_PREREQ([2.64]) # gl_COMPILER_OPTION_IF(OPTION, [IF-SUPPORTED], [IF-NOT-SUPPORTED], # [PROGRAM = AC_LANG_PROGRAM()]) @@ -23,8 +16,6 @@ m4_ifdef([AS_VAR_APPEND], # The effects of this macro depend on the current language (_AC_LANG). AC_DEFUN([gl_COMPILER_OPTION_IF], [ -dnl FIXME: gl_Warn must be used unquoted until we can assume Autoconf -dnl 2.64 or newer. AS_VAR_PUSHDEF([gl_Warn], [gl_cv_warn_[]_AC_LANG_ABBREV[]_$1])dnl AS_VAR_PUSHDEF([gl_Flags], [_AC_LANG_PREFIX[]FLAGS])dnl AS_LITERAL_IF([$1], @@ -34,13 +25,13 @@ case $gl_positive in -Wno-*) gl_positive=-W`expr "X$gl_positive" : 'X-Wno-\(.*\)'` ;; esac m4_pushdef([gl_Positive], [$gl_positive])])dnl -AC_CACHE_CHECK([whether _AC_LANG compiler handles $1], m4_defn([gl_Warn]), [ +AC_CACHE_CHECK([whether _AC_LANG compiler handles $1], [gl_Warn], [ gl_save_compiler_FLAGS="$gl_Flags" - gl_AS_VAR_APPEND(m4_defn([gl_Flags]), + AS_VAR_APPEND(m4_defn([gl_Flags]), [" $gl_unknown_warnings_are_errors ]m4_defn([gl_Positive])["]) - AC_LINK_IFELSE([m4_default([$4], [AC_LANG_PROGRAM([])])], - [AS_VAR_SET(gl_Warn, [yes])], - [AS_VAR_SET(gl_Warn, [no])]) + AC_LINK_IFELSE([m4_default([$4], [AC_LANG_PROGRAM([[]])])], + [AS_VAR_SET([gl_Warn], [yes])], + [AS_VAR_SET([gl_Warn], [no])]) gl_Flags="$gl_save_compiler_FLAGS" ]) AS_VAR_IF(gl_Warn, [yes], [$2], [$3]) @@ -59,8 +50,7 @@ AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS], [_AC_LANG_DISPATCH([$0], _AC_LANG, $@)]) # Specialization for _AC_LANG = C. This macro can be AC_REQUIREd. -# Use of m4_defun rather than AC_DEFUN works around a bug in autoconf < 2.63b. -m4_defun([gl_UNKNOWN_WARNINGS_ARE_ERRORS(C)], +AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS(C)], [ AC_LANG_PUSH([C]) gl_UNKNOWN_WARNINGS_ARE_ERRORS_IMPL @@ -68,14 +58,21 @@ m4_defun([gl_UNKNOWN_WARNINGS_ARE_ERRORS(C)], ]) # Specialization for _AC_LANG = C++. This macro can be AC_REQUIREd. -# Use of m4_defun rather than AC_DEFUN works around a bug in autoconf < 2.63b. -m4_defun([gl_UNKNOWN_WARNINGS_ARE_ERRORS(C++)], +AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS(C++)], [ AC_LANG_PUSH([C++]) gl_UNKNOWN_WARNINGS_ARE_ERRORS_IMPL AC_LANG_POP([C++]) ]) +# Specialization for _AC_LANG = Objective C. This macro can be AC_REQUIREd. +AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS(Objective C)], +[ + AC_LANG_PUSH([Objective C]) + gl_UNKNOWN_WARNINGS_ARE_ERRORS_IMPL + AC_LANG_POP([Objective C]) +]) + AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS_IMPL], [gl_COMPILER_OPTION_IF([-Werror -Wunknown-warning-option], [gl_unknown_warnings_are_errors='-Wunknown-warning-option -Werror'], @@ -84,16 +81,18 @@ AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS_IMPL], # gl_WARN_ADD(OPTION, [VARIABLE = WARN_CFLAGS/WARN_CXXFLAGS], # [PROGRAM = AC_LANG_PROGRAM()]) # ----------------------------------------------------------- -# Adds parameter to WARN_CFLAGS/WARN_CXXFLAGS if the compiler supports it -# when compiling PROGRAM. For example, gl_WARN_ADD([-Wparentheses]). +# Adds OPTION to VARIABLE (which defaults to WARN_CFLAGS or WARN_CXXFLAGS) +# if the compiler supports it when compiling PROGRAM. # # If VARIABLE is a variable name, AC_SUBST it. # # The effects of this macro depend on the current language (_AC_LANG). +# +# Example: gl_WARN_ADD([-Wparentheses]). AC_DEFUN([gl_WARN_ADD], [AC_REQUIRE([gl_UNKNOWN_WARNINGS_ARE_ERRORS(]_AC_LANG[)]) gl_COMPILER_OPTION_IF([$1], - [gl_AS_VAR_APPEND(m4_if([$2], [], [[WARN_]_AC_LANG_PREFIX[FLAGS]], [[$2]]), [" $1"])], + [AS_VAR_APPEND(m4_if([$2], [], [[WARN_]_AC_LANG_PREFIX[FLAGS]], [[$2]]), [" $1"])], [], [$3]) m4_ifval([$2], @@ -101,6 +100,77 @@ m4_ifval([$2], [AC_SUBST([WARN_]_AC_LANG_PREFIX[FLAGS])])dnl ]) + +# gl_CC_INHIBIT_WARNINGS +# sets and substitutes a variable GL_CFLAG_INHIBIT_WARNINGS, to a $(CC) option +# that reverts all preceding -W* options, if available. +# This is expected to be '-w' at least on gcc, clang, AIX xlc, xlclang, Sun cc, +# "compile cl" (MSVC), "compile clang-cl" (MSVC-compatible clang). Or it can be +# empty. +AC_DEFUN([gl_CC_INHIBIT_WARNINGS], +[ + AC_REQUIRE([AC_PROG_CC]) + AC_CACHE_CHECK([for C compiler option to inhibit all warnings], + [gl_cv_cc_winhibit], + [rm -f conftest* + echo 'int dummy;' > conftest.c + AC_TRY_COMMAND([${CC-cc} $CFLAGS $CPPFLAGS -c conftest.c 2>conftest1.err]) >/dev/null + AC_TRY_COMMAND([${CC-cc} $CFLAGS $CPPFLAGS -w -c conftest.c 2>conftest2.err]) >/dev/null + if test $? = 0 && test `wc -l < conftest1.err` = `wc -l < conftest2.err`; then + gl_cv_cc_winhibit='-w' + else + gl_cv_cc_winhibit=none + fi + rm -f conftest* + ]) + case "$gl_cv_cc_winhibit" in + none) GL_CFLAG_INHIBIT_WARNINGS='' ;; + *) + GL_CFLAG_INHIBIT_WARNINGS="$gl_cv_cc_winhibit" + dnl If all warnings are inhibited, there's no point in having the GCC + dnl analyzer enabled. This saves RAM requirements and CPU consumption. + gl_WARN_ADD([-fno-analyzer], [GL_CFLAG_INHIBIT_WARNINGS]) + ;; + esac + AC_SUBST([GL_CFLAG_INHIBIT_WARNINGS]) +]) + +# gl_CXX_INHIBIT_WARNINGS +# sets and substitutes a variable GL_CXXFLAG_INHIBIT_WARNINGS, to a $(CC) option +# that reverts all preceding -W* options, if available. +AC_DEFUN([gl_CXX_INHIBIT_WARNINGS], +[ + dnl Requires AC_PROG_CXX or gl_PROG_ANSI_CXX. + if test -n "$CXX" && test "$CXX" != no; then + AC_CACHE_CHECK([for C++ compiler option to inhibit all warnings], + [gl_cv_cxx_winhibit], + [rm -f conftest* + echo 'int dummy;' > conftest.cc + AC_TRY_COMMAND([${CXX-c++} $CXXFLAGS $CPPFLAGS -c conftest.cc 2>conftest1.err]) >/dev/null + AC_TRY_COMMAND([${CXX-c++} $CXXFLAGS $CPPFLAGS -w -c conftest.cc 2>conftest2.err]) >/dev/null + if test $? = 0 && test `wc -l < conftest1.err` = `wc -l < conftest2.err`; then + gl_cv_cxx_winhibit='-w' + else + gl_cv_cxx_winhibit=none + fi + rm -f conftest* + ]) + case "$gl_cv_cxx_winhibit" in + none) GL_CXXFLAG_INHIBIT_WARNINGS='' ;; + *) + GL_CXXFLAG_INHIBIT_WARNINGS="$gl_cv_cxx_winhibit" + dnl If all warnings are inhibited, there's no point in having the GCC + dnl analyzer enabled. This saves RAM requirements and CPU consumption. + gl_WARN_ADD([-fno-analyzer], [GL_CXXFLAG_INHIBIT_WARNINGS]) + ;; + esac + else + GL_CXXFLAG_INHIBIT_WARNINGS='' + fi + AC_SUBST([GL_CXXFLAG_INHIBIT_WARNINGS]) +]) + + # Local Variables: # mode: autoconf # End: diff --git a/man/.gitignore b/man/.gitignore new file mode 100644 index 0000000..ba0d22a --- /dev/null +++ b/man/.gitignore @@ -0,0 +1 @@ +*.? diff --git a/man/atsign.1in b/man/atsign.1in new file mode 100644 index 0000000..6cc1123 --- /dev/null +++ b/man/atsign.1in @@ -0,0 +1,41 @@ +.TH ATSIGN "1" "2023-10-14" "@PACKAGE_STRING@" + +.SH NAME +@ATSIGN@ \- delay until a specified time + +.SH SYNOPSIS +.B @ATSIGN@ +[\fI\,date\/\fR]\fI\,time\/\fR + +.SH OPTIONS + +.TP +\fB\-F\fR, \fB\-\-list\-formats\fR +List all supported date and time formats + +.TP +\fB\-h\fR, \fB\-\-help\fR +Show this help information + +.TP +\fB\-V\fR, \fB\-\-version\fR +Show version information + +.SH "REPORTING BUGS" +Report bugs to <@PACKAGE_BUGREPORT@>. + +.SH COPYRIGHT +Copyright \(co 2023 P. J. McDermott +.PP +@ is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +.PP +@ is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License +along with @. If not, see <http://www.gnu.org/licenses/>. diff --git a/man/local.mk b/man/local.mk new file mode 100644 index 0000000..65814fe --- /dev/null +++ b/man/local.mk @@ -0,0 +1,43 @@ +# Process this file with automake to produce an input makefile. +# +# Copyright (C) 2018, 2023 P. J. McDermott +# +# This file is part of @ +# +# @ is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# @ is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with @. If not, see <http://www.gnu.org/licenses/>. + +man1_sources = \ + %reldir%/atsign.1in +man1_MANS = $(man1_sources:.1in=.1) + +CLEANFILES += \ + $(man1_MANS) +EXTRA_DIST += \ + $(man1_sources) + +if ENABLE_SYMLINK +atsign = @$(EXEEXT) +else !ENABLE_SYMLINK +atsign = $$(printf 'atsign' | sed '$(transform); s/$$/$(EXEEXT)/') +endif +man_subst = sed \ + -e "s|[@]PACKAGE_STRING[@]|$(PACKAGE_STRING)|g" \ + -e "s|[@]PACKAGE_BUGREPORT[@]|$(PACKAGE_BUGREPORT)|g" \ + -e "s|[@]ATSIGN[@]|$(atsign)|g" + +SUFFIXES += .1in .1 + +.1in.1: + $(AM_V_GEN)$(MKDIR_P) "$$(dirname $@)" + $(AM_V_at)$(man_subst) $< >$@ diff --git a/po/.gitignore b/po/.gitignore new file mode 100644 index 0000000..532f823 --- /dev/null +++ b/po/.gitignore @@ -0,0 +1,11 @@ +/Makefile.in.in +/Makevars.template +/Rules-quot +/*.sed +/POTFILES +/*.pot +/*.po +/*.mo +/*@boldquot.header +/*@quot.header +/stamp-po diff --git a/po/Makevars b/po/Makevars new file mode 100644 index 0000000..1753e43 --- /dev/null +++ b/po/Makevars @@ -0,0 +1,26 @@ +# Variables to be inserted into Makefile + +DOMAIN = $(PACKAGE) + +subdir = po +top_builddir = .. + +XGETTEXT_OPTIONS = --add-comments=TRANSLATORS --keyword=_ + +COPYRIGHT_HOLDER = P. J. McDermott + +PACKAGE_GNU = no + +MSGID_BUGS_ADDRESS = mailto:pj@pehjota.net + +EXTRA_LOCALE_CATEGORIES = + +USE_MSGCTXT = no + +MSGMERGE_OPTIONS = + +MSGINIT_OPTIONS = + +PO_DEPENDS_ON_POT = yes + +DIST_DEPENDS_ON_UPDATE_PO = yes diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..5472064 --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,2 @@ +src/datetime.c +src/main.c diff --git a/scripts/update-m4.sh b/scripts/update-m4.sh new file mode 100755 index 0000000..e0d500d --- /dev/null +++ b/scripts/update-m4.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# Script to update M4 files from Gnulib, GNU Autoconf Archive, and GNU Coreutils +# +# Copyright (C) 2023 P. J. McDermott +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. + +set -eu + +SV_CGIT='https://git.savannah.gnu.org/cgit' + +for f in m4/gl_*.m4; do + wget -O "${f}" "${SV_CGIT}/gnulib.git/plain/m4/${f#m4/gl_}" +done + +for f in m4/ax_*.m4; do + wget -O "${f}" "${SV_CGIT}/autoconf-archive.git/plain/${f}" +done + +wget -O - "${SV_CGIT}/coreutils.git/plain/configure.ac" | sed -n ' + /^# Copyright /,/^dnl Written /{ + p; + s/^dnl Written .*$//p; + }; + /^# gl_GCC_VERSION_IFELSE/,/^$/p; + ' | sed '$d' 1>'m4/gcc_version.m4' diff --git a/src/datetime.c b/src/datetime.c index 079266b..72f0219 100644 --- a/src/datetime.c +++ b/src/datetime.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 P. J. McDermott + * Copyright (C) 2021, 2022 P. J. McDermott * * This file is part of @ * @@ -18,6 +18,9 @@ */ #define _XOPEN_SOURCE +/* glibc requires these for timegm(): */ +#define _DEFAULT_SOURCE +#define _BSD_SOURCE #include <ctype.h> #include <errno.h> @@ -28,80 +31,33 @@ #include <string.h> #include <time.h> #include "datetime.h" +#include "formats.h" +#include "i18n.h" -/* IMPORTANT: All of the format strings in each array must be padded with spaces - * to be of equal lengths */ -static const char *DATETIME_DATE_FMTS_[] = { - " ", /* */ - " %a ", /* Wed */ - " %Y-%m-%d ", /* 1969-12-31 */ - " %Y/%m/%d ", /* 1969/12/31 */ - " %m-%d-%Y ", /* 12-31-1969 */ - " %m-%d ", /* 12-31 */ - " %m/%d/%Y ", /* 12/31/1969 */ - " %m/%d ", /* 12/31 */ - " %e %b %Y ", /* 31 Dec 1969 */ - " %e %b ", /* 31 Dec */ - " %d-%b-%Y ", /* 31-Dec-1969 */ - " %d-%b ", /* 31-Dec */ - " %d/%b/%Y ", /* 31/Dec/1969 */ - " %d/%b ", /* 31/Dec */ - " %a %e %b %Y ", /* Wed 31 Dec 1969 */ - " %a %e %b ", /* Wed 31 Dec */ - " %a %d-%b-%Y ", /* Wed 31-Dec-1969 */ - " %a %d-%b ", /* Wed 31-Dec */ - " %a %d/%b/%Y ", /* Wed 31/Dec/1969 */ - " %a %d/%b ", /* Wed 31/Dec */ - " %a, %e %b %Y ", /* Wed, 31 Dec 1969 */ - " %a, %e %b ", /* Wed, 31 Dec */ - " %a, %d-%b-%Y ", /* Wed, 31-Dec-1969 */ - " %a, %d-%b ", /* Wed, 31-Dec */ - " %a, %d/%b/%Y ", /* Wed, 31/Dec/1969 */ - " %a, %d/%b ", /* Wed, 31/Dec */ - " %b %e, %Y ", /* Dec 31, 1969 */ - " %b %e ", /* Dec 31 */ - " %b-%d-%Y ", /* Dec-31-1969 */ - " %b-%d ", /* Dec-31 */ - " %b/%d/%Y ", /* Dec/31/1969 */ - " %b/%d ", /* Dec/31 */ - " %a %b %e, %Y ", /* Wed Dec 31, 1969 */ - " %a %b %e ", /* Wed Dec 31 */ - " %a %b-%d-%Y ", /* Wed Dec-31-1969 */ - " %a %b-%d ", /* Wed Dec-31 */ - " %a %b/%d/%Y ", /* Wed Dec/31/1969 */ - " %a %b/%d ", /* Wed Dec/31 */ - " %a, %b %e, %Y ", /* Wed, Dec 31, 1969 */ - " %a, %b %e ", /* Wed, Dec 31 */ - " %a, %b-%d-%Y ", /* Wed, Dec-31-1969 */ - " %a, %b-%d ", /* Wed, Dec-31 */ - " %a, %b/%d/%Y ", /* Wed, Dec/31/1969 */ - " %a, %b/%d ", /* Wed, Dec/31 */ -}; -static const char *DATETIME_TIME_FMTS_[] = { - " %I:%M:%S %p ", /* 7:00:01 PM */ - " %H:%M:%S ", /* 19:00:01 */ - " %I:%M %p ", /* 7:00 PM */ - " %H:%M ", /* 19:00 */ - " %I%M%S %p ", /* 70001 PM */ - " %H%M%S ", /* 190001 */ - " %I%M %p ", /* 700 PM */ - " %H%M ", /* 1900 */ -}; -static const char *DATETIME_MISC_FMTS_[] = { - /* ISO 8601: 1969-12-31 delimited by "T" */ - " %Y-%m-%dT%H:%M:%S ", - " %Y-%m-%dT%H:%M ", - /* ISO 8601: 19691231 delimited by "T" */ - " %Y%m%dT%H%M%S ", - " %Y%m%dT%H%M ", - /* 19691231 */ - " %Y%m%d %I%M%S %p ", - " %Y%m%d %H%M%S ", - " %Y%m%d %I%M %p ", - " %Y%m%d %H%M ", - /* Wed Dec 31 19:00:01 1969 */ - " %a %b %d %H:%M:%S %Y ", -}; +static void +_datetime_buf_cpy_p(char *dst, const char *src) +{ + for (; *src != '\0'; ++src, ++dst) { + if (*src == '^') { + *dst = ' '; + } else { + *dst = *src; + } + } + *dst = '\0'; +} + +static void +_datetime_buf_cpy_f(char *dst, const char *src) +{ + for (; *src != '\0'; ++src, ++dst) { + if (*src == '^') { + ++src; + } + *dst = *src; + } + *dst = '\0'; +} static void _datetime_reset_tm(struct tm *tm) @@ -117,58 +73,64 @@ _datetime_reset_tm(struct tm *tm) } static void -_datetime_normalize(struct tm *arg_tm, time_t *arg_sec) +_datetime_normalize(struct tm *now_tm, struct tm *arg_tm, time_t *arg_sec) { - time_t now_sec; - struct tm *now_tm; - int wday; + int days_in_mon[12] = {31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31}; + + if (now_tm->tm_year % 4 == 0 && (now_tm->tm_year % 100 != 0 || + now_tm->tm_year % 400 == 0)) { + days_in_mon[1] = 29; + } if (arg_tm->tm_sec == INT_MIN) { arg_tm->tm_sec = 0; } if (arg_tm->tm_mday == INT_MIN && arg_tm->tm_wday == INT_MIN) { /* No date specified; try today. */ - now_sec = time(NULL); - now_tm = localtime(&now_sec); arg_tm->tm_year = now_tm->tm_year; arg_tm->tm_mon = now_tm->tm_mon; arg_tm->tm_mday = now_tm->tm_mday; arg_tm->tm_wday = now_tm->tm_wday; *arg_sec = mktime(arg_tm); - if (*arg_sec <= mktime(now_tm)) { + if (difftime(*arg_sec, mktime(now_tm)) <= 0) { /* Specified time already happened today; use tomorrow. - * Adding the number of seconds in a day is a shortcut - * that ignores leap seconds. One better method would - * be to increment tm_mday % days in tm_mon, etc. */ - *arg_sec += 60 * 60 * 24; - now_tm = localtime(arg_sec); - arg_tm->tm_year = now_tm->tm_year; - arg_tm->tm_mon = now_tm->tm_mon; - arg_tm->tm_mday = now_tm->tm_mday; + */ + ++arg_tm->tm_mday; + arg_tm->tm_wday = (arg_tm->tm_wday + 1) % 7; + if (arg_tm->tm_mday > days_in_mon[arg_tm->tm_mon]) { + arg_tm->tm_mday = 1; + ++arg_tm->tm_mon; + if (arg_tm->tm_mon >= 12) { + arg_tm->tm_mon = 0; + ++arg_tm->tm_year; + } + } *arg_sec = mktime(arg_tm); } } else if (arg_tm->tm_mday == INT_MIN) { - /* Only a weekday specified; try tomorrow or next week. Uses - * same shortcut as above. */ - wday = arg_tm->tm_wday; - now_sec = time(NULL); - now_tm = localtime(&now_sec); + /* Only a weekday specified; try tomorrow or next week. */ arg_tm->tm_year = now_tm->tm_year; arg_tm->tm_mon = now_tm->tm_mon; arg_tm->tm_mday = now_tm->tm_mday; - arg_tm->tm_wday = now_tm->tm_wday; - if (wday <= now_tm->tm_wday) { - wday += 7; + if (arg_tm->tm_wday <= now_tm->tm_wday) { + arg_tm->tm_mday += 7; + } + arg_tm->tm_mday += arg_tm->tm_wday - now_tm->tm_wday; + if (arg_tm->tm_mday > days_in_mon[arg_tm->tm_mon]) { + arg_tm->tm_mday -= days_in_mon[arg_tm->tm_mon]; + ++arg_tm->tm_mon; + if (arg_tm->tm_mon >= 12) { + arg_tm->tm_mon = 0; + ++arg_tm->tm_year; + } } *arg_sec = mktime(arg_tm); - *arg_sec += (60 * 60 * 24) * (wday - now_tm->tm_wday); } else if (arg_tm->tm_year == INT_MIN) { /* No year specified; try this year. */ - now_sec = time(NULL); - now_tm = localtime(&now_sec); arg_tm->tm_year = now_tm->tm_year; *arg_sec = mktime(arg_tm); - if (*arg_sec <= mktime(now_tm)) { + if (difftime(*arg_sec, mktime(now_tm)) <= 0) { /* Specified time already happened this year; use next * year. */ ++arg_tm->tm_year; @@ -180,71 +142,118 @@ _datetime_normalize(struct tm *arg_tm, time_t *arg_sec) } int -datetime_parse(const char *input, time_t *arg_sec) +datetime_parse(struct tm *now_tm, const char *input, + struct tm *arg_tm, time_t *arg_sec) { int date_fmt_len; int time_fmt_len; + int misc_fmt_len; + int fmt_len; char *fmt_buf; size_t d; size_t t; char *end; - struct tm arg_tm; + time_t sec; + bool got; - date_fmt_len = strlen(DATETIME_DATE_FMTS_[0]); - time_fmt_len = strlen(DATETIME_TIME_FMTS_[0]); - fmt_buf = calloc(date_fmt_len + time_fmt_len + 1, sizeof(*fmt_buf)); + date_fmt_len = strlen(FORMATS_DATE[0]); + time_fmt_len = strlen(FORMATS_TIME[0]); + misc_fmt_len = strlen(FORMATS_MISC[0]); + fmt_len = date_fmt_len + time_fmt_len; + if (misc_fmt_len > fmt_len) { + fmt_len = misc_fmt_len; + } + fmt_buf = calloc(fmt_len + 1, sizeof(*fmt_buf)); if (fmt_buf == NULL) { - fprintf(stderr, "Failed to allocate buffer: %s\n", + fprintf(stderr, _("Failed to allocate buffer: %s\n"), strerror(errno)); return -1; } - for (d = 0; d < sizeof(DATETIME_DATE_FMTS_) / - sizeof(DATETIME_DATE_FMTS_[0]); ++d) { - memcpy(fmt_buf, DATETIME_DATE_FMTS_[d], date_fmt_len); - for (t = 0; t < sizeof(DATETIME_TIME_FMTS_) / - sizeof(DATETIME_TIME_FMTS_[0]); ++t) { - memcpy(fmt_buf + date_fmt_len, DATETIME_TIME_FMTS_[t], - time_fmt_len); - _datetime_reset_tm(&arg_tm); - end = strptime(input, fmt_buf, &arg_tm); - if (end != NULL && *end == '\0') { + sec = *arg_sec; /* GCC is dumb. */ + got = false; + for (t = 0; t < sizeof(FORMATS_TIME) / sizeof(FORMATS_TIME[0]); ++t) { + _datetime_buf_cpy_p(fmt_buf, FORMATS_TIME[t]); + _datetime_reset_tm(arg_tm); + end = strptime(input, fmt_buf, arg_tm); + if (end != NULL && *end == '\0') { + _datetime_normalize(now_tm, arg_tm, arg_sec); + if (arg_tm->tm_year >= 0) { free(fmt_buf); - _datetime_normalize(&arg_tm, arg_sec); return 0; } + sec = *arg_sec; + got = true; } } - for (t = 0; t < sizeof(DATETIME_TIME_FMTS_) / - sizeof(DATETIME_TIME_FMTS_[0]); ++t) { - memcpy(fmt_buf, DATETIME_TIME_FMTS_[t], time_fmt_len); - for (d = 0; d < sizeof(DATETIME_DATE_FMTS_) / - sizeof(DATETIME_DATE_FMTS_[0]); ++d) { - memcpy(fmt_buf + time_fmt_len, DATETIME_DATE_FMTS_[d], - date_fmt_len); - _datetime_reset_tm(&arg_tm); - end = strptime(input, fmt_buf, &arg_tm); + for (d = 0; d < sizeof(FORMATS_DATE) / sizeof(FORMATS_DATE[0]); ++d) { + if (FORMATS_DATE[d][0] == '\0') { + break; + } + _datetime_buf_cpy_p(fmt_buf, FORMATS_DATE[d]); + for (t = 0; t < sizeof(FORMATS_TIME) / sizeof(FORMATS_TIME[0]); + ++t) { + _datetime_buf_cpy_p(fmt_buf + date_fmt_len, + FORMATS_TIME[t]); + _datetime_reset_tm(arg_tm); + end = strptime(input, fmt_buf, arg_tm); if (end != NULL && *end == '\0') { - free(fmt_buf); - _datetime_normalize(&arg_tm, arg_sec); - return 0; + _datetime_normalize(now_tm, arg_tm, arg_sec); + if (arg_tm->tm_year >= 0) { + free(fmt_buf); + return 0; + } + sec = *arg_sec; + got = true; + } + } + } + for (t = 0; t < sizeof(FORMATS_TIME) / sizeof(FORMATS_TIME[0]); ++t) { + _datetime_buf_cpy_p(fmt_buf, FORMATS_TIME[t]); + for (d = 0; d < sizeof(FORMATS_DATE) / sizeof(FORMATS_DATE[0]); + ++d) { + if (FORMATS_DATE[d][0] == '\0') { + break; + } + _datetime_buf_cpy_p(fmt_buf + time_fmt_len, + FORMATS_DATE[d]); + _datetime_reset_tm(arg_tm); + end = strptime(input, fmt_buf, arg_tm); + if (end != NULL && *end == '\0') { + _datetime_normalize(now_tm, arg_tm, arg_sec); + if (arg_tm->tm_year >= 0) { + free(fmt_buf); + return 0; + } + sec = *arg_sec; + got = true; } } } - for (d = 0; d < sizeof(DATETIME_MISC_FMTS_) / - sizeof(DATETIME_MISC_FMTS_[0]) ; ++d) { - _datetime_reset_tm(&arg_tm); - end = strptime(input, DATETIME_MISC_FMTS_[d], &arg_tm); + for (d = 0; d < sizeof(FORMATS_MISC) / sizeof(FORMATS_MISC[0]); ++d) { + _datetime_buf_cpy_p(fmt_buf, FORMATS_MISC[d]); + _datetime_reset_tm(arg_tm); + end = strptime(input, fmt_buf, arg_tm); if (end != NULL && *end == '\0') { - free(fmt_buf); - _datetime_normalize(&arg_tm, arg_sec); - return 0; + _datetime_normalize(now_tm, arg_tm, arg_sec); + if (arg_tm->tm_year >= 0) { + free(fmt_buf); + return 0; + } + sec = *arg_sec; + got = true; } } - free(fmt_buf); - fputs("Unknown date format\n", stderr); - return -1; + if (got == true) { + *arg_sec = sec; + free(fmt_buf); + return 0; + } else { + fputs(_("Unknown date format\n"), stderr); + free(fmt_buf); + return -1; + } } static void @@ -283,30 +292,36 @@ _datetime_strftime(const char *fmts[], const struct tm *tm, char **out, int resized; if (*i == 0) { - *buf_sz = strlen(fmts[0]); + *buf_sz = strlen(fmts[0]) + 1; *buf = calloc(*buf_sz, sizeof(**buf)); if (*buf == NULL) { - fprintf(stderr, "Failed to allocate buffer: %s\n", + fprintf(stderr, _("Failed to allocate buffer: %s\n"), strerror(errno)); return -1; } *out = calloc(*buf_sz, sizeof(**out)); if (*out == NULL) { - fprintf(stderr, "Failed to allocate buffer: %s\n", + fprintf(stderr, _("Failed to allocate buffer: %s\n"), strerror(errno)); return -1; } } + if (fmts[*i][0] == '\0') { + ++*i; + } + + _datetime_buf_cpy_f(*out, fmts[*i]); + resized = 0; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" - while (strftime(*buf, *buf_sz, fmts[*i], tm) == 0) { + while (strftime(*buf, *buf_sz, *out, tm) == 0) { #pragma GCC diagnostic pop ++*buf_sz; *buf = realloc(*buf, *buf_sz * sizeof(**buf)); if (*buf == NULL) { - fprintf(stderr, "Failed to allocate buffer: %s\n", + fprintf(stderr, _("Failed to allocate buffer: %s\n"), strerror(errno)); return -1; } @@ -315,7 +330,7 @@ _datetime_strftime(const char *fmts[], const struct tm *tm, char **out, if (resized > 0) { *out = realloc(*out, *buf_sz * sizeof(**out)); if (*out == NULL) { - fprintf(stderr, "Failed to allocate buffer: %s\n", + fprintf(stderr, _("Failed to allocate buffer: %s\n"), strerror(errno)); return -1; } @@ -331,7 +346,7 @@ int datetime_strftime_date(const struct tm *tm, char **out, size_t *buf_sz, char **buf, size_t *i) { - if (*i >= sizeof(DATETIME_DATE_FMTS_) / sizeof(DATETIME_DATE_FMTS_[0])){ + if (*i >= sizeof(FORMATS_DATE) / sizeof(FORMATS_DATE[0])) { if (*out != NULL) { free(*out); } @@ -341,14 +356,14 @@ datetime_strftime_date(const struct tm *tm, char **out, size_t *buf_sz, return 0; } - return _datetime_strftime(DATETIME_DATE_FMTS_, tm, out, buf_sz, buf, i); + return _datetime_strftime(FORMATS_DATE, tm, out, buf_sz, buf, i); } int datetime_strftime_time(const struct tm *tm, char **out, size_t *buf_sz, char **buf, size_t *i) { - if (*i >= sizeof(DATETIME_TIME_FMTS_) / sizeof(DATETIME_TIME_FMTS_[0])){ + if (*i >= sizeof(FORMATS_TIME) / sizeof(FORMATS_TIME[0])) { if (*out != NULL) { free(*out); } @@ -358,14 +373,14 @@ datetime_strftime_time(const struct tm *tm, char **out, size_t *buf_sz, return 0; } - return _datetime_strftime(DATETIME_TIME_FMTS_, tm, out, buf_sz, buf, i); + return _datetime_strftime(FORMATS_TIME, tm, out, buf_sz, buf, i); } int datetime_strftime_misc(const struct tm *tm, char **out, size_t *buf_sz, char **buf, size_t *i) { - if (*i >= sizeof(DATETIME_MISC_FMTS_) / sizeof(DATETIME_MISC_FMTS_[0])){ + if (*i >= sizeof(FORMATS_MISC) / sizeof(FORMATS_MISC[0])) { if (*out != NULL) { free(*out); } @@ -375,5 +390,5 @@ datetime_strftime_misc(const struct tm *tm, char **out, size_t *buf_sz, return 0; } - return _datetime_strftime(DATETIME_MISC_FMTS_, tm, out, buf_sz, buf, i); + return _datetime_strftime(FORMATS_MISC, tm, out, buf_sz, buf, i); } diff --git a/src/datetime.h b/src/datetime.h index edff8f7..1094cf8 100644 --- a/src/datetime.h +++ b/src/datetime.h @@ -23,7 +23,8 @@ #include <time.h> int -datetime_parse(const char *input, time_t *arg_sec); +datetime_parse(struct tm *now_tm, const char *input, + struct tm *arg_tm, time_t *arg_sec); int datetime_strftime_date(const struct tm *tm, char **out, size_t *out_sz, diff --git a/src/formats.h b/src/formats.h new file mode 100644 index 0000000..c88f731 --- /dev/null +++ b/src/formats.h @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2021, 2022 P. J. McDermott + * + * This file is part of @ + * + * @ is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * @ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with @. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef FORMATS_H_ +#define FORMATS_H_ + +/* + * IMPORTANT: + * + * All of the format strings in each array must be padded with spaces to be of + * equal lengths. + * + * Conversion specifications are used by both strptime() and strftime(). As + * required by the former function, there shall be "white-space or other non- + * alphanumeric characters between any two conversion specifications unless all + * of the adjacent conversion specifications convert a known, fixed number of + * characters." [POSIX] The "^" character may be used to separate conversion + * specifications that should be separated (by a space) when parsing and + * adjacent when formatting for printing. For example, " %I^%M %p " will be + * changed to " %I %M %p " when parsing and to " %I%M %p " when formatting for + * printing. + */ + +static const char *FORMATS_DATE[] = { + " %a ", /* Wed */ + " %a. ", /* Wed. */ + " %Y-%m-%d ", /* 1969-12-31 */ + " %Y/%m/%d ", /* 1969/12/31 */ + " %Y.%m.%d ", /* 1969.12.31 */ + " %m-%d-%Y ", /* 12-31-1969 */ + " %m-%d ", /* 12-31 */ + " %m/%d/%Y ", /* 12/31/1969 */ + " %m/%d ", /* 12/31 */ + " %m.%d.%Y ", /* 12.31.1969 */ + " %m.%d ", /* 12.31 */ + " %e %b %Y ", /* 31 Dec 1969 */ + " %e %b. %Y ", /* 31 Dec. 1969 */ + " %e %b ", /* 31 Dec */ + " %e %b. ", /* 31 Dec. */ + " %d-%b-%Y ", /* 31-Dec-1969 */ + " %d-%b ", /* 31-Dec */ + " %d/%b/%Y ", /* 31/Dec/1969 */ + " %d/%b ", /* 31/Dec */ + " %a %e %b %Y ", /* Wed 31 Dec 1969 */ + " %a %e %b. %Y ", /* Wed 31 Dec. 1969 */ + " %a %e %b ", /* Wed 31 Dec */ + " %a %e %b. ", /* Wed 31 Dec. */ + " %a %d-%b-%Y ", /* Wed 31-Dec-1969 */ + " %a %d-%b ", /* Wed 31-Dec */ + " %a %d/%b/%Y ", /* Wed 31/Dec/1969 */ + " %a %d/%b ", /* Wed 31/Dec */ + " %a. %e %b %Y ", /* Wed. 31 Dec 1969 */ + " %a. %e %b. %Y ", /* Wed. 31 Dec. 1969 */ + " %a. %e %b ", /* Wed. 31 Dec */ + " %a. %e %b. ", /* Wed. 31 Dec. */ + " %a. %d-%b-%Y ", /* Wed. 31-Dec-1969 */ + " %a. %d-%b ", /* Wed. 31-Dec */ + " %a. %d/%b/%Y ", /* Wed. 31/Dec/1969 */ + " %a. %d/%b ", /* Wed. 31/Dec */ + " %a, %e %b %Y ", /* Wed, 31 Dec 1969 */ + " %a, %e %b. %Y ", /* Wed, 31 Dec. 1969 */ + " %a, %e %b ", /* Wed, 31 Dec */ + " %a, %e %b. ", /* Wed, 31 Dec. */ + " %a, %d-%b-%Y ", /* Wed, 31-Dec-1969 */ + " %a, %d-%b ", /* Wed, 31-Dec */ + " %a, %d/%b/%Y ", /* Wed, 31/Dec/1969 */ + " %a, %d/%b ", /* Wed, 31/Dec */ + " %b %e, %Y ", /* Dec 31, 1969 */ + " %b. %e, %Y ", /* Dec. 31, 1969 */ + " %b %e ", /* Dec 31 */ + " %b. %e ", /* Dec. 31 */ + " %b-%d-%Y ", /* Dec-31-1969 */ + " %b-%d ", /* Dec-31 */ + " %b/%d/%Y ", /* Dec/31/1969 */ + " %b/%d ", /* Dec/31 */ + " %a %b %e, %Y ", /* Wed Dec 31, 1969 */ + " %a %b. %e, %Y ", /* Wed Dec. 31, 1969 */ + " %a %b %e ", /* Wed Dec 31 */ + " %a %b. %e ", /* Wed Dec. 31 */ + " %a %b-%d-%Y ", /* Wed Dec-31-1969 */ + " %a %b-%d ", /* Wed Dec-31 */ + " %a %b/%d/%Y ", /* Wed Dec/31/1969 */ + " %a %b/%d ", /* Wed Dec/31 */ + " %a. %b %e, %Y ", /* Wed. Dec 31, 1969 */ + " %a. %b. %e, %Y ", /* Wed. Dec. 31, 1969 */ + " %a. %b %e ", /* Wed. Dec 31 */ + " %a. %b. %e ", /* Wed. Dec. 31 */ + " %a. %b-%d-%Y ", /* Wed. Dec-31-1969 */ + " %a. %b-%d ", /* Wed. Dec-31 */ + " %a. %b/%d/%Y ", /* Wed. Dec/31/1969 */ + " %a. %b/%d ", /* Wed. Dec/31 */ + " %a, %b %e, %Y ", /* Wed, Dec 31, 1969 */ + " %a, %b. %e, %Y ", /* Wed, Dec. 31, 1969 */ + " %a, %b %e ", /* Wed, Dec 31 */ + " %a, %b. %e ", /* Wed, Dec. 31 */ + " %a, %b-%d-%Y ", /* Wed, Dec-31-1969 */ + " %a, %b-%d ", /* Wed, Dec-31 */ + " %a, %b/%d/%Y ", /* Wed, Dec/31/1969 */ + " %a, %b/%d ", /* Wed, Dec/31 */ + "", /* End of parsing formats */ + " %A ", /* Wednesday */ + " %A %e %b %Y ", /* Wednesday 31 Dec 1969 */ + " %A %e %b. %Y ", /* Wednesday 31 Dec. 1969 */ + " %A %e %b ", /* Wednesday 31 Dec */ + " %A %e %b. ", /* Wednesday 31 Dec. */ + " %A %d-%b-%Y ", /* Wednesday 31-Dec-1969 */ + " %A %d-%b ", /* Wednesday 31-Dec */ + " %A %d/%b/%Y ", /* Wednesday 31/Dec/1969 */ + " %A %d/%b ", /* Wednesday 31/Dec */ + " %A, %e %b %Y ", /* Wednesday, 31 Dec 1969 */ + " %A, %e %b. %Y ", /* Wednesday, 31 Dec. 1969 */ + " %A, %e %b ", /* Wednesday, 31 Dec */ + " %A, %e %b. ", /* Wednesday, 31 Dec. */ + " %A, %d-%b-%Y ", /* Wednesday, 31-Dec-1969 */ + " %A, %d-%b ", /* Wednesday, 31-Dec */ + " %A, %d/%b/%Y ", /* Wednesday, 31/Dec/1969 */ + " %A, %d/%b ", /* Wednesday, 31/Dec */ + " %A %b %e, %Y ", /* Wednesday Dec 31, 1969 */ + " %A %b. %e, %Y ", /* Wednesday Dec. 31, 1969 */ + " %A %b %e ", /* Wednesday Dec 31 */ + " %A %b. %e ", /* Wednesday Dec. 31 */ + " %A %b-%d-%Y ", /* Wednesday Dec-31-1969 */ + " %A %b-%d ", /* Wednesday Dec-31 */ + " %A %b/%d/%Y ", /* Wednesday Dec/31/1969 */ + " %A %b/%d ", /* Wednesday Dec/31 */ + " %A, %b %e, %Y ", /* Wednesday, Dec 31, 1969 */ + " %A, %b. %e, %Y ", /* Wednesday, Dec. 31, 1969 */ + " %A, %b %e ", /* Wednesday, Dec 31 */ + " %A, %b. %e ", /* Wednesday, Dec. 31 */ + " %A, %b-%d-%Y ", /* Wednesday, Dec-31-1969 */ + " %A, %b-%d ", /* Wednesday, Dec-31 */ + " %A, %b/%d/%Y ", /* Wednesday, Dec/31/1969 */ + " %A, %b/%d ", /* Wednesday, Dec/31 */ + " %e %B %Y ", /* 31 December 1969 */ + " %e %B ", /* 31 December */ + " %d-%B-%Y ", /* 31-December-1969 */ + " %d-%B ", /* 31-December */ + " %d/%B/%Y ", /* 31/December/1969 */ + " %d/%B ", /* 31/December */ + " %a %e %B %Y ", /* Wed 31 December 1969 */ + " %a %e %B ", /* Wed 31 December */ + " %a %d-%B-%Y ", /* Wed 31-December-1969 */ + " %a %d-%B ", /* Wed 31-December */ + " %a %d/%B/%Y ", /* Wed 31/December/1969 */ + " %a %d/%B ", /* Wed 31/December */ + " %a. %e %B %Y ", /* Wed. 31 December 1969 */ + " %a. %e %B ", /* Wed. 31 December */ + " %a. %d-%B-%Y ", /* Wed. 31-December-1969 */ + " %a. %d-%B ", /* Wed. 31-December */ + " %a. %d/%B/%Y ", /* Wed. 31/December/1969 */ + " %a. %d/%B ", /* Wed. 31/December */ + " %a, %e %B %Y ", /* Wed, 31 December 1969 */ + " %a, %e %B ", /* Wed, 31 December */ + " %a, %d-%B-%Y ", /* Wed, 31-December-1969 */ + " %a, %d-%B ", /* Wed, 31-December */ + " %a, %d/%B/%Y ", /* Wed, 31/December/1969 */ + " %a, %d/%B ", /* Wed, 31/December */ + " %B %e, %Y ", /* December 31, 1969 */ + " %B %e ", /* December 31 */ + " %B-%d-%Y ", /* December-31-1969 */ + " %B-%d ", /* December-31 */ + " %B/%d/%Y ", /* December/31/1969 */ + " %B/%d ", /* December/31 */ + " %a %B %e, %Y ", /* Wed December 31, 1969 */ + " %a %B %e ", /* Wed December 31 */ + " %a %B-%d-%Y ", /* Wed December-31-1969 */ + " %a %B-%d ", /* Wed December-31 */ + " %a %B/%d/%Y ", /* Wed December/31/1969 */ + " %a %B/%d ", /* Wed December/31 */ + " %a. %B %e, %Y ", /* Wed. December 31, 1969 */ + " %a. %B %e ", /* Wed. December 31 */ + " %a. %B-%d-%Y ", /* Wed. December-31-1969 */ + " %a. %B-%d ", /* Wed. December-31 */ + " %a. %B/%d/%Y ", /* Wed. December/31/1969 */ + " %a. %B/%d ", /* Wed. December/31 */ + " %a, %B %e, %Y ", /* Wed, December 31, 1969 */ + " %a, %B %e ", /* Wed, December 31 */ + " %a, %B-%d-%Y ", /* Wed, December-31-1969 */ + " %a, %B-%d ", /* Wed, December-31 */ + " %a, %B/%d/%Y ", /* Wed, December/31/1969 */ + " %a, %B/%d ", /* Wed, December/31 */ + " %A %e %B %Y ", /* Wednesday 31 December 1969 */ + " %A %e %B ", /* Wednesday 31 December */ + " %A %d-%B-%Y ", /* Wednesday 31-December-1969 */ + " %A %d-%B ", /* Wednesday 31-December */ + " %A %d/%B/%Y ", /* Wednesday 31/December/1969 */ + " %A %d/%B ", /* Wednesday 31/December */ + " %A, %e %B %Y ", /* Wednesday, 31 December 1969 */ + " %A, %e %B ", /* Wednesday, 31 December */ + " %A, %d-%B-%Y ", /* Wednesday, 31-December-1969 */ + " %A, %d-%B ", /* Wednesday, 31-December */ + " %A, %d/%B/%Y ", /* Wednesday, 31/December/1969 */ + " %A, %d/%B ", /* Wednesday, 31/December */ + " %A %B %e, %Y ", /* Wednesday December 31, 1969 */ + " %A %B %e ", /* Wednesday December 31 */ + " %A %B-%d-%Y ", /* Wednesday December-31-1969 */ + " %A %B-%d ", /* Wednesday December-31 */ + " %A %B/%d/%Y ", /* Wednesday December/31/1969 */ + " %A %B/%d ", /* Wednesday December/31 */ + " %A, %B %e, %Y ", /* Wednesday, December 31, 1969 */ + " %A, %B %e ", /* Wednesday, December 31 */ + " %A, %B-%d-%Y ", /* Wednesday, December-31-1969 */ + " %A, %B-%d ", /* Wednesday, December-31 */ + " %A, %B/%d/%Y ", /* Wednesday, December/31/1969 */ + " %A, %B/%d ", /* Wednesday, December/31 */ +}; + +static const char *FORMATS_TIME[] = { + " %I:%M:%S %p ", /* 7:00:01 PM */ + " %H:%M:%S ", /* 19:00:01 */ + " %I:%M %p ", /* 7:00 PM */ + " %H:%M ", /* 19:00 */ + " %I.%M.%S %p ", /* 7.00.01 PM */ + " %H.%M.%S ", /* 19.00.01 */ + " %I.%M %p ", /* 7.00 PM */ + " %H.%M ", /* 19.00 */ + " %I^%M^%S %p ", /* 70001 PM */ + " %H^%M^%S ", /* 190001 */ + " %I^%M %p ", /* 700 PM */ + " %H^%M ", /* 1900 */ +}; + +static const char *FORMATS_MISC[] = { + /* ISO 8601: 1969-12-31 delimited by "T" */ + " %Y-%m-%dT%H:%M:%S ", + " %Y-%m-%dT%H:%M ", + /* ISO 8601: 19691231 delimited by "T" */ + " %Y^%m^%dT%H^%M^%S ", + " %Y^%m^%dT%H^%M ", + /* 19691231 */ + " %Y^%m^%d %I^%M^%S %p ", + " %Y^%m^%d %H^%M^%S ", + " %Y^%m^%d %I^%M %p ", + " %Y^%m^%d %H^%M ", + /* Wed Dec 31 19:00:01 1969 */ + " %a %b %d %H:%M:%S %Y ", +}; + +#endif /* FORMATS_H_ */ diff --git a/src/i18n.h b/src/i18n.h new file mode 100644 index 0000000..4d8ee76 --- /dev/null +++ b/src/i18n.h @@ -0,0 +1,11 @@ +#ifdef ENABLE_NLS + +#include <libintl.h> + +#define _(msgid) gettext(msgid) + +#else + +#define _(msgid) (msgid) + +#endif diff --git a/src/local.mk b/src/local.mk index 0882580..1012945 100644 --- a/src/local.mk +++ b/src/local.mk @@ -1,4 +1,6 @@ -@_SOURCES += \ +atsign_SOURCES += \ %reldir%/datetime.c \ %reldir%/datetime.h \ + %reldir%/formats.h \ + %reldir%/i18n.h \ %reldir%/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 P. J. McDermott + * Copyright (C) 2021, 2022 P. J. McDermott * * This file is part of @ * @@ -17,6 +17,12 @@ * along with @. If not, see <http://www.gnu.org/licenses/>. */ +#include "config.h" + +#ifdef ENABLE_NLS +#include <locale.h> +#endif + #include <errno.h> #include <stdbool.h> #include <stdio.h> @@ -24,8 +30,8 @@ #include <string.h> #include <time.h> #include <unistd.h> -#include "config.h" #include "datetime.h" +#include "i18n.h" #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG #include <getopt.h> @@ -34,7 +40,7 @@ extern const char *PACKAGE_VERSION_GIT; #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG -struct option LONGOPTS_[] = { +static const struct option LONGOPTS_[] = { { .name = "list-formats", .has_arg = 0, @@ -59,63 +65,83 @@ struct option LONGOPTS_[] = { static void _print_usage(FILE *stream, const char *program_name) { - fprintf(stream, "Usage: %s [date]time\n", program_name); + fprintf(stream, _("Usage: %s [date]time\n"), program_name); } static void _print_help(const char *program_name) { _print_usage(stdout, program_name); - puts("Options:"); #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG - puts("\t-F, --list-formats List all supported date and time formats"); - puts("\t-h, --help Show this help information"); - puts("\t-V, --version Show version information"); + puts(_("Options:\n" + " -F, --list-formats List all supported date and time formats\n" + " -h, --help Show this help information\n" + " -V, --version Show version information")); #else - puts("\t-F List all supported date and time formats"); - puts("\t-h Show this help information"); - puts("\t-V Show version information"); + puts(_("Options:\n" + " -F List all supported date and time formats\n" + " -h Show this help information\n" + " -V Show version information")); #endif } -static void -_list_formats(void) +#if defined(ENABLE_TESTS) && ENABLE_TESTS +static struct tm * +_set_now(char buf[]) { - time_t tim; - struct tm *tm; - char *out; - size_t buf_sz; - char *buf; - size_t i; + struct tm tm; + time_t t; + struct tm *tm_p; - tim = time(NULL); - tm = localtime(&tim); + sscanf(buf, "%04d-%02d-%02d %02d:%02d:%02d", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec); + tm.tm_year -= 1900; + tm.tm_mon -= 1; + tm.tm_wday = -1; + tm.tm_yday = -1; + tm.tm_isdst = -1; - puts("Time formats:"); + t = mktime(&tm); + tm_p = localtime(&t); + + return tm_p; +} +#endif + +static void +_list_formats(const struct tm *tm) +{ + char *out; + size_t buf_sz; + char *buf; + size_t i; + + puts(_("Time formats:")); i = 0; while (datetime_strftime_time(tm, &out, &buf_sz, &buf, &i) > 0) { if (out[0] == '\0') { continue; } - printf(" * %s\n", out); + printf(_(" * %s\n"), out); } - puts("Date formats:"); + puts(_("Date formats:")); i = 0; while (datetime_strftime_date(tm, &out, &buf_sz, &buf, &i) > 0) { if (out[0] == '\0') { continue; } - printf(" * %s\n", out); + printf(_(" * %s\n"), out); } - puts("Additional formats:"); + puts(_("Additional formats:")); i = 0; while (datetime_strftime_misc(tm, &out, &buf_sz, &buf, &i) > 0) { if (out[0] == '\0') { continue; } - printf(" * %s\n", out); + printf(_(" * %s\n"), out); } } @@ -123,13 +149,12 @@ static void _print_version(void) { printf("@ (atsign) %s%s\n", PACKAGE_VERSION, PACKAGE_VERSION_GIT); - puts("Copyright (C) 2021 P. J. McDermott"); - puts("License GPLv3+: GNU GPL version 3 or later " - "<http://gnu.org/licenses/gpl.html>."); - puts("This is free software: you are free to change and redistribute " - "it."); - puts("There is NO WARRANTY, to the extent permitted by law.\n"); - printf("Please report bugs to <%s>.\n", PACKAGE_BUGREPORT); + puts(_("Copyright (C) 2021, 2022 P. J. McDermott\n" + "License GPLv3+: GNU GPL version 3 or later " + "<http://gnu.org/licenses/gpl.html>.\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n")); + printf(_("Please report bugs to <%s>.\n"), PACKAGE_BUGREPORT); } static char * @@ -148,7 +173,7 @@ _concat_args(int argc, char * const argv[]) buf = calloc(buf_l, sizeof(*buf)); if (buf == NULL) { - fprintf(stderr, "Failed to allocate buffer: %s\n", + fprintf(stderr, _("Failed to allocate buffer: %s\n"), strerror(errno)); return NULL; } @@ -168,37 +193,76 @@ _concat_args(int argc, char * const argv[]) int main(int argc, char * const argv[]) { - int opt; - char *buf; - time_t arg; - time_t now; - time_t dif; +#if defined(ENABLE_TESTS) && ENABLE_TESTS + bool dbg; +#endif + bool fmt; + int opt; + char *buf; + time_t now; + struct tm *now_tm; + struct tm arg_tm; + time_t arg; + double dif; +#ifdef ENABLE_NLS + bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); +#ifdef HAVE_BIND_TEXTDOMAIN_CODESET + bind_textdomain_codeset(PACKAGE, "UTF-8"); +#endif + textdomain(PACKAGE); + setlocale(LC_ALL, ""); +#endif + +#if defined(ENABLE_TESTS) && ENABLE_TESTS + dbg = false; +#endif + fmt = false; optind = 1; opterr = 0; + now = time(NULL); + now_tm = localtime(&now); + #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG - while ((opt = getopt_long(argc, argv, "FhV", LONGOPTS_, NULL)) > 0) { +#if defined(ENABLE_TESTS) && ENABLE_TESTS + while ((opt = getopt_long(argc, argv, "FhVd:", LONGOPTS_, NULL)) > 0) { +#else + while ((opt = getopt_long(argc, argv, "FhV:", LONGOPTS_, NULL)) > 0) { +#endif +#else +#if defined(ENABLE_TESTS) && ENABLE_TESTS + while ((opt = getopt(argc, argv, "FhVd:")) > 0) { #else - while ((opt = getopt(argc, argv, "FhV")) > 0) { + while ((opt = getopt(argc, argv, "FhV:")) > 0) { +#endif #endif switch (opt) { case 'F': - _list_formats(); - return EXIT_SUCCESS; + fmt = true; + break; case 'h': _print_help(argv[0]); return EXIT_SUCCESS; case 'V': _print_version(); return EXIT_SUCCESS; +#if defined(ENABLE_TESTS) && ENABLE_TESTS + case 'd': + dbg = true; + now_tm = _set_now(optarg); + if (now_tm == NULL) { + return EXIT_FAILURE; + } + break; +#endif default: _print_usage(stderr, argv[0]); #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG - fprintf(stderr, "Try '%s --help' for more " - "information.\n", argv[0]); + fprintf(stderr, _("Try '%s --help' for more " + "information.\n"), argv[0]); #else - fprintf(stderr, "Try '%s -h' for more " - "information.\n", argv[0]); + fprintf(stderr, _("Try '%s -h' for more " + "information.\n"), argv[0]); #endif return EXIT_FAILURE; } @@ -206,34 +270,52 @@ main(int argc, char * const argv[]) argc -= optind; argv += optind; + if (fmt == true) { + _list_formats(now_tm); + return EXIT_SUCCESS; + } + buf = _concat_args(argc, argv); if (buf == NULL) { return EXIT_FAILURE; } - if (datetime_parse(buf, &arg) < 0) { + if (datetime_parse(now_tm, buf, &arg_tm, &arg) < 0) { free(buf); return EXIT_FAILURE; } free(buf); - time(&now); - dif = arg - now; +#if defined(ENABLE_TESTS) && ENABLE_TESTS + if (dbg == true) { + printf("%04d-%02d-%02d %02d:%02d:%02d\n", + arg_tm.tm_year + 1900, + arg_tm.tm_mon + 1, + arg_tm.tm_mday, + arg_tm.tm_hour, + arg_tm.tm_min, + arg_tm.tm_sec); + return EXIT_SUCCESS; + } +#endif + + now = time(NULL); + dif = difftime(arg, now); if (dif >= 1000 * 24 * 60 * 60) { - fputs("Date too far in the future\n", stderr); + fputs(_("Date too far in the future\n"), stderr); return EXIT_FAILURE; } setvbuf(stdout, NULL, _IONBF, 0); - while (arg > now) { + while (dif > 0) { printf("\r%03d:%02d:%02d:%02d", (int) dif / 60 / 60 / 24, (int) dif / 60 / 60 % 24, (int) dif / 60 % 60, (int) dif % 60); sleep(1); - time(&now); - dif = arg - now; + now = time(NULL); + dif = difftime(arg, now); } printf("\r000:00:00:00\n"); diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..7e563b8 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,2 @@ +*.log +*.trs diff --git a/tests/aux/tap-functions.sh b/tests/aux/tap-functions.sh new file mode 100644 index 0000000..e2414b7 --- /dev/null +++ b/tests/aux/tap-functions.sh @@ -0,0 +1,229 @@ +# -*- shell-script -*- +# +# Copyright (C) 2011-2022 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# Helper functions used by TAP-producing tests of the Automake testsuite. + +# +# IMPORTANT: All the functions defined in this file can *not* be used +# from within a subshell, unless explicitly noted otherwise. +# + +# The counts of the TAP test results seen so far: total count and +# per-result counts. +tap_count_=0 +tap_pass_count_=0 +tap_skip_count_=0 +tap_fail_count_=0 +tap_xfail_count_=0 +tap_xpass_count_=0 + +# not COMMAND [ARGS...] +# --------------------- +# Run the given command and invert its exit status. +not () { ! "$@"; } + +# plan_ [unknown|later|lazy|now|NUMBER-OF-PLANNED-TESTS] +# ------------------------------------------------------ +# Print a TAP plan for the given number of tests. This must be called +# before reporting any test result. If called with the special argument +# "unknown" or "later", it will do nothing, expecting the calling script +# to declare the plan later. If called with the special argument "lazy" +# or "now", it will print a TAP plan that accounts for the number of tests +# seen so far. +plan_ () +{ + if test $# -eq 0; then + bailout_ "plan_: missing argument" + elif test $# -ge 2; then + bailout_ "plan_: too many arguments" + elif test x"$planned_" != x"none" && test x"$planned_" != x"later"; then + bailout_ "plan_: called to many times" + elif test x"$1" = x"unknown" || test x"$1" = x"later"; then + # This means we want to get back later to declaring the TAP plan. + planned_=later + return 0 + elif test x"$1" = x"lazy" || test x"$1" = x"now"; then + planned_=$tap_count_ # Number of test results seen so far. + elif test $1 -ge 0; then + planned_=$1 + else + bailout_ "plan_: invalid argument '$1'" + fi + echo "1..$planned_" +} +planned_=none + +# diag_ [EXPLANATION] +# ------------------ +# Report the given text as TAP diagnostic. Assumes the string denoting +# TAP diagnostic lines is stored in the '$diag_string_' variable; this is +# done to allow better interplay with TAP drivers that allow such a string +# to be configured. +diag_ () +{ + test $# -eq 0 || echo "$diag_string_ $*" +} + +# Used by the 'diag_' function above. User-overridable. +diag_string_="#" + +# warn_ [EXPLANATION] +# ------------------ +# Give a warning (using TAP diagnostic). +warn_ () +{ + case $# in + 0) diag_ "WARNING: (unknown warning)";; + *) diag_ "WARNING: $*";; + esac +} + +# result_ RESULT [-D DIRECTIVE] [-r REASON] [--] [DESCRIPTION...] +# --------------------------------------------------------------- +# Report a test case with the given RESULT (valid values are "ok" and +# "not ok") and the given DESCRIPTION (if any). If DIRECTIVE is given +# and non-empty (valid values being "TODO" and "SKIP"), it will be +# reported too, with the REASON (if given) appended. +result_ () +{ + test $# -gt 0 || bailout_ "result_: missing argument" + tap_result_=$1; shift + case $tap_result_ in + "ok"|"not ok") ;; + *) bailout_ "result_: invalid result '$tap_result'" ;; + esac + tap_directive_= tap_reason_= + while test $# -gt 0; do + case $1 in + -D|--directive) tap_directive_=$2; shift;; + -r|--reason) tap_reason_=$2; shift;; + --) shift; break;; + -*) bailout_ "result_: invalid option '$1'";; + *) break;; + esac + shift + done + case $tap_directive_ in + ""|TODO|SKIP) ;; + *) bailout_ "result_: invalid directive '$directive_'" ;; + esac + tap_count_=$(($tap_count_ + 1)) + case $tap_result_,$tap_directive_ in + ok,) # Passed. + tap_pass_count_=$(($tap_pass_count_ + 1)) ;; + not\ ok,TODO) # Expected failure. + tap_xfail_count_=$(($tap_xfail_count_ + 1)) ;; + not\ ok,*) # Failed. + tap_fail_count_=$(($tap_fail_count_ + 1)) ;; + ok,TODO) # Unexpected pass. + tap_xpass_count_=$(($tap_xpass_count_ + 1)) ;; + ok,SKIP) # Skipped. + tap_skip_count_=$(($tap_skip_count_ + 1)) ;; + *) # Can't happen. + bailout_ "internal error in 'result_'" ;; + esac + tap_text_="$tap_result_ $tap_count_" + if test x"$*" != x; then + tap_text_="$tap_text_ - $*" + fi + if test x"$tap_directive_" != x; then + tap_text_="$tap_text_ # $tap_directive_"${tap_reason_:+" $tap_reason_"} + fi + printf '%s\n' "$tap_text_" +} + +# Shorthands for common usages of 'result_'. +ok_ () { result_ 'ok' ${1+"$@"}; } +not_ok_ () { result_ 'not ok' ${1+"$@"}; } +skip_ () { result_ 'ok' -D SKIP ${1+"$@"}; } + +# skip_row_ COUNT [-r REASON] [--] [DESCRIPTION...] +# ------------------------------------------------- +# Report a COUNT of skipped test, with the given reason and descriptions +# (if any). Useful to avoid cascade failures in case a fair number of +# tests depend on an earlier one that failed. +skip_row_ () +{ + skip_count_=$1; shift + for i_ in $(seq_ $skip_count_); do skip_ ${1+"$@"}; done +} + +# skip_all_ [REASON ...] +# ---------------------- +# Skip all the tests in a test script. Must be used before calling 'plan_' +# or reporting any test result. Can't be used from within a subshell. +skip_all_ () +{ + echo "1..0 # SKIP" ${1+"$@"} + planned_=0 + exit 0 +} + +# bailout_ [REASON ...] +# --------------------- +# Stop the execution of the current test suite right now, due to an +# unrecoverable error. Can be called at any point, but cannot be used +# from within a subshell. +bailout_ () +{ + echo 'Bail out!' ${1+"$@"} + exit 99 +} + +# fatal_ [REASON ...] +# ------------------- +# Same as 'bailout_'; for compatibility with 'plain-functions.sh'. +fatal_ () +{ + bailout_ ${1+"$@"} +} + +# framework_failure_ [REASON ...] +# ------------------------------- +# Stop the execution of the current test suite right now, due to an +# unrecoverable error in the set-up of the test case. Can be called +# at any point, but cannot be used from within a subshell. +framework_failure_ () +{ + bailout_ "set-up failure"${1+": $*"} +} + +# command_ok_ TEST-DESCRIPTION [OPTIONS..] [--] CMD [ARGS...] +# ----------------------------------------------------------- +# Helper subroutine for when a TAP result must be determined by the +# outcome of a command. +command_ok_ () +{ + tap_directive_= tap_reason_= + test $# -gt 0 || bailout_ "command_ok_: missing argument" + tap_description_=$1; shift + while test $# -gt 0; do + case $1 in + -D|--directive) tap_directive_=$2; shift;; + -r|--reason) tap_reason_=$2; shift;; + --) shift; break;; + -*) bailout_ "command_ok_: invalid option '$1'";; + *) break;; + esac + shift + done + tap_result_="ok"; "$@" || tap_result_="not ok" + result_ "$tap_result_" -D "$tap_directive_" -r "$tap_reason_" \ + -- "$tap_description_" +} + +: diff --git a/tests/disabled.sh b/tests/disabled.sh new file mode 100755 index 0000000..53caf82 --- /dev/null +++ b/tests/disabled.sh @@ -0,0 +1,2 @@ +printf 'Test suite support disabled\n' 1>&2 +exit 1 diff --git a/tests/formats.exp b/tests/formats.exp new file mode 100644 index 0000000..20b49c9 --- /dev/null +++ b/tests/formats.exp @@ -0,0 +1,204 @@ +Time formats: + * 03:04:05 AM + * 03:04:05 + * 03:04 AM + * 03:04 + * 03.04.05 AM + * 03.04.05 + * 03.04 AM + * 03.04 + * 030405 AM + * 030405 + * 0304 AM + * 0304 +Date formats: + * Fri + * Fri. + * 1970-01-02 + * 1970/01/02 + * 1970.01.02 + * 01-02-1970 + * 01-02 + * 01/02/1970 + * 01/02 + * 01.02.1970 + * 01.02 + * 2 Jan 1970 + * 2 Jan. 1970 + * 2 Jan + * 2 Jan. + * 02-Jan-1970 + * 02-Jan + * 02/Jan/1970 + * 02/Jan + * Fri 2 Jan 1970 + * Fri 2 Jan. 1970 + * Fri 2 Jan + * Fri 2 Jan. + * Fri 02-Jan-1970 + * Fri 02-Jan + * Fri 02/Jan/1970 + * Fri 02/Jan + * Fri. 2 Jan 1970 + * Fri. 2 Jan. 1970 + * Fri. 2 Jan + * Fri. 2 Jan. + * Fri. 02-Jan-1970 + * Fri. 02-Jan + * Fri. 02/Jan/1970 + * Fri. 02/Jan + * Fri, 2 Jan 1970 + * Fri, 2 Jan. 1970 + * Fri, 2 Jan + * Fri, 2 Jan. + * Fri, 02-Jan-1970 + * Fri, 02-Jan + * Fri, 02/Jan/1970 + * Fri, 02/Jan + * Jan 2, 1970 + * Jan. 2, 1970 + * Jan 2 + * Jan. 2 + * Jan-02-1970 + * Jan-02 + * Jan/02/1970 + * Jan/02 + * Fri Jan 2, 1970 + * Fri Jan. 2, 1970 + * Fri Jan 2 + * Fri Jan. 2 + * Fri Jan-02-1970 + * Fri Jan-02 + * Fri Jan/02/1970 + * Fri Jan/02 + * Fri. Jan 2, 1970 + * Fri. Jan. 2, 1970 + * Fri. Jan 2 + * Fri. Jan. 2 + * Fri. Jan-02-1970 + * Fri. Jan-02 + * Fri. Jan/02/1970 + * Fri. Jan/02 + * Fri, Jan 2, 1970 + * Fri, Jan. 2, 1970 + * Fri, Jan 2 + * Fri, Jan. 2 + * Fri, Jan-02-1970 + * Fri, Jan-02 + * Fri, Jan/02/1970 + * Fri, Jan/02 + * Friday + * Friday 2 Jan 1970 + * Friday 2 Jan. 1970 + * Friday 2 Jan + * Friday 2 Jan. + * Friday 02-Jan-1970 + * Friday 02-Jan + * Friday 02/Jan/1970 + * Friday 02/Jan + * Friday, 2 Jan 1970 + * Friday, 2 Jan. 1970 + * Friday, 2 Jan + * Friday, 2 Jan. + * Friday, 02-Jan-1970 + * Friday, 02-Jan + * Friday, 02/Jan/1970 + * Friday, 02/Jan + * Friday Jan 2, 1970 + * Friday Jan. 2, 1970 + * Friday Jan 2 + * Friday Jan. 2 + * Friday Jan-02-1970 + * Friday Jan-02 + * Friday Jan/02/1970 + * Friday Jan/02 + * Friday, Jan 2, 1970 + * Friday, Jan. 2, 1970 + * Friday, Jan 2 + * Friday, Jan. 2 + * Friday, Jan-02-1970 + * Friday, Jan-02 + * Friday, Jan/02/1970 + * Friday, Jan/02 + * 2 January 1970 + * 2 January + * 02-January-1970 + * 02-January + * 02/January/1970 + * 02/January + * Fri 2 January 1970 + * Fri 2 January + * Fri 02-January-1970 + * Fri 02-January + * Fri 02/January/1970 + * Fri 02/January + * Fri. 2 January 1970 + * Fri. 2 January + * Fri. 02-January-1970 + * Fri. 02-January + * Fri. 02/January/1970 + * Fri. 02/January + * Fri, 2 January 1970 + * Fri, 2 January + * Fri, 02-January-1970 + * Fri, 02-January + * Fri, 02/January/1970 + * Fri, 02/January + * January 2, 1970 + * January 2 + * January-02-1970 + * January-02 + * January/02/1970 + * January/02 + * Fri January 2, 1970 + * Fri January 2 + * Fri January-02-1970 + * Fri January-02 + * Fri January/02/1970 + * Fri January/02 + * Fri. January 2, 1970 + * Fri. January 2 + * Fri. January-02-1970 + * Fri. January-02 + * Fri. January/02/1970 + * Fri. January/02 + * Fri, January 2, 1970 + * Fri, January 2 + * Fri, January-02-1970 + * Fri, January-02 + * Fri, January/02/1970 + * Fri, January/02 + * Friday 2 January 1970 + * Friday 2 January + * Friday 02-January-1970 + * Friday 02-January + * Friday 02/January/1970 + * Friday 02/January + * Friday, 2 January 1970 + * Friday, 2 January + * Friday, 02-January-1970 + * Friday, 02-January + * Friday, 02/January/1970 + * Friday, 02/January + * Friday January 2, 1970 + * Friday January 2 + * Friday January-02-1970 + * Friday January-02 + * Friday January/02/1970 + * Friday January/02 + * Friday, January 2, 1970 + * Friday, January 2 + * Friday, January-02-1970 + * Friday, January-02 + * Friday, January/02/1970 + * Friday, January/02 +Additional formats: + * 1970-01-02T03:04:05 + * 1970-01-02T03:04 + * 19700102T030405 + * 19700102T0304 + * 19700102 030405 AM + * 19700102 030405 + * 19700102 0304 AM + * 19700102 0304 + * Fri Jan 02 03:04:05 1970 diff --git a/tests/formats.sh b/tests/formats.sh new file mode 100755 index 0000000..576b96a --- /dev/null +++ b/tests/formats.sh @@ -0,0 +1,43 @@ +# Tests `@ -F` output +# +# Copyright (C) 2022 P. J. McDermott +# +# This file is part of @ +# +# @ is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# @ is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with @. If not, see <http://www.gnu.org/licenses/>. + +set -eu + +. "${TOP_SRCDIR}/tests/aux/tap-functions.sh" + +plan_ 1 + +exp="$(cat "${0%.sh}.exp")" +got="$("${TOP_BUILDDIR}/atsign" -d '1970-01-02 03:04:05' -F)" + +if [ x"${got}" = x"${exp}" ]; then + ok_ -- 'formats list' + exit 0 +fi +not_ok_ -- 'formats list' + +IFS=' +' + +for line in $(printf '%s\n' "${got}" | diff -u "${0%.sh}.exp" '-' | sed '1,2d') +do + diag_ "${line}" +done + +exit 0 diff --git a/tests/local.mk b/tests/local.mk new file mode 100644 index 0000000..84eb79e --- /dev/null +++ b/tests/local.mk @@ -0,0 +1,21 @@ +tests = \ + %reldir%/formats.sh \ + %reldir%/parse.sh +if ENABLE_TESTS +TESTS = $(tests) +else +TESTS = %reldir%/disabled.sh +endif + +TEST_EXTENSIONS = .sh +SH_LOG_DRIVER = \ + AM_TAP_AWK='$(AWK)' \ + TOP_SRCDIR="$(abs_top_srcdir)" \ + TOP_BUILDDIR="$(abs_top_builddir)" \ + $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh +EXTRA_DIST += \ + $(tests) \ + %reldir%/disabled.sh \ + %reldir%/formats.exp \ + $(top_srcdir)/build-aux/tap-driver.sh \ + %reldir%/aux/tap-functions.sh diff --git a/tests/parse.sh b/tests/parse.sh new file mode 100755 index 0000000..eadd2cf --- /dev/null +++ b/tests/parse.sh @@ -0,0 +1,77 @@ +# Tests @'s date/time parsing +# +# Copyright (C) 2022 P. J. McDermott +# +# This file is part of @ +# +# @ is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# @ is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with @. If not, see <http://www.gnu.org/licenses/>. + +set -eu + +. "${TOP_SRCDIR}/tests/aux/tap-functions.sh" + +now='1969-12-31 23:59:30' +tgt='1970-01-01 00:00:00' + +fmts="$(TZ=UTC0 "${TOP_BUILDDIR}/atsign" -d "${tgt}" -F)" +time_fmts="$(printf '%s\n' "${fmts}" | \ + sed -n '/^Time formats:$/,/^Date formats:$/s/^ \* //p')" +date_fmts="$(printf '%s\n' "${fmts}" | \ + sed -n '/^Date formats:$/,/^Additional formats:$/s/^ \* //p')" +misc_fmts="$(printf '%s\n' "${fmts}" | \ + sed -n '/^Additional formats:$/,$s/^ \* //p')" + +plan_ $((\ + $(printf '%s\n' "${time_fmts}" | wc -l) + \ + $(printf '%s\n' "${time_fmts}" | wc -l) * \ + $(printf '%s\n' "${date_fmts}" | wc -l) * \ + 2 + \ + $(printf '%s\n' "${misc_fmts}" | wc -l) \ + )) + +IFS=' +' + +for fmt in $(printf '%s\n' "${fmts}"); do + diag_ "${fmt}" +done + +test_fmt() +{ + now="${1}" + tgt="${2}" + fmt="${3}" + shift 3 + + got="$(TZ=UTC0 "${TOP_BUILDDIR}/atsign" -d "${now}" "${fmt}" 2>&1)" || : + if [ x"${got}" = x"${tgt}" ]; then + ok_ -- "${fmt}" + else + not_ok_ -- "${fmt}" + diag_ " Failed test '${fmt}'" + diag_ " got: '${got}'" + diag_ " expected: '${tgt}'" + fi +} + +for time_fmt in ${time_fmts}; do + test_fmt "${now}" "${tgt}" "${time_fmt}" + for date_fmt in ${date_fmts}; do + test_fmt "${now}" "${tgt}" "${date_fmt} ${time_fmt}" + test_fmt "${now}" "${tgt}" "${time_fmt} ${date_fmt}" + done +done +for misc_fmt in ${misc_fmts}; do + test_fmt "${now}" "${tgt}" "${misc_fmt}" +done |