Hi, Argp provides line wrapping of help and usage output. For this it maintains a buffer in the struct argp_fmtstream_t. During line breaking the buffer's contents grow due to the inserted spaces needed to indent the lines (function __argp_fmtstream_update in lib/argp-fmtstream.c). This breaks once the buffer is full and produces malformatted output. Example program:
#include <config.h> #include <argp.h> static struct argp_option options[] = { {"test", 't', 0, 0, "1\n2\n3\n4\n5\n6"}, {0} }; static struct argp argp = {options}; int main (int argc, char **argv) { argp_parse(&argp, argc, argv, 0, 0, 0); return 0; } This gives $ ./report --help Usage: report [OPTION...] -t, --test 1 2 3 4 5 6 -?, --help give this help list --usage give a short usage message The indentation that should prefix the '6' erroneously prefixes the 'Usage' line. The suggested fix to __argp_fmtstream_update works by making it flush everything ASAP. This way no formatting of content inside the buffer is needed. This passes the existing tests (test-argp.c, test-argp-2.sh). In addition it produces identical --help and --usage output for GNU tar's large option table (see attached file tar.c). Further Nitpicking: before overlong words that exceed the line length one gets a spurious newline before the word: static struct argp_option options[] = { {"x", 'x', 0, 0, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}, {0} }; will produce the --help output Usage: bigword [OPTION...] -x, --x xxxxxxx(...) -?, --help give this help list --usage give a short usage message This is fixed by flushing at the end of indent_to in argp-help.c Cheers, Simon
#include <config.h> #include <argp.h> //* Options. */ enum { ACLS_OPTION = CHAR_MAX + 1, ATIME_PRESERVE_OPTION, BACKUP_OPTION, CHECK_DEVICE_OPTION, CHECKPOINT_OPTION, CHECKPOINT_ACTION_OPTION, DELAY_DIRECTORY_RESTORE_OPTION, HARD_DEREFERENCE_OPTION, DELETE_OPTION, FORCE_LOCAL_OPTION, FULL_TIME_OPTION, GROUP_OPTION, GROUP_MAP_OPTION, IGNORE_COMMAND_ERROR_OPTION, IGNORE_FAILED_READ_OPTION, INDEX_FILE_OPTION, KEEP_DIRECTORY_SYMLINK_OPTION, KEEP_NEWER_FILES_OPTION, LEVEL_OPTION, LZIP_OPTION, LZMA_OPTION, LZOP_OPTION, MODE_OPTION, MTIME_OPTION, NEWER_MTIME_OPTION, NO_ACLS_OPTION, NO_AUTO_COMPRESS_OPTION, NO_CHECK_DEVICE_OPTION, NO_DELAY_DIRECTORY_RESTORE_OPTION, NO_IGNORE_COMMAND_ERROR_OPTION, NO_OVERWRITE_DIR_OPTION, NO_QUOTE_CHARS_OPTION, NO_SAME_OWNER_OPTION, NO_SAME_PERMISSIONS_OPTION, NO_SEEK_OPTION, NO_SELINUX_CONTEXT_OPTION, NO_XATTR_OPTION, NUMERIC_OWNER_OPTION, OCCURRENCE_OPTION, OLD_ARCHIVE_OPTION, ONE_FILE_SYSTEM_OPTION, ONE_TOP_LEVEL_OPTION, OVERWRITE_DIR_OPTION, OVERWRITE_OPTION, OWNER_OPTION, OWNER_MAP_OPTION, PAX_OPTION, POSIX_OPTION, PRESERVE_OPTION, QUOTE_CHARS_OPTION, QUOTING_STYLE_OPTION, RECORD_SIZE_OPTION, RECURSIVE_UNLINK_OPTION, REMOVE_FILES_OPTION, RESTRICT_OPTION, RMT_COMMAND_OPTION, RSH_COMMAND_OPTION, SAME_OWNER_OPTION, SELINUX_CONTEXT_OPTION, SHOW_DEFAULTS_OPTION, SHOW_OMITTED_DIRS_OPTION, SHOW_SNAPSHOT_FIELD_RANGES_OPTION, SHOW_TRANSFORMED_NAMES_OPTION, SKIP_OLD_FILES_OPTION, SORT_OPTION, HOLE_DETECTION_OPTION, SPARSE_VERSION_OPTION, STRIP_COMPONENTS_OPTION, SUFFIX_OPTION, TEST_LABEL_OPTION, TOTALS_OPTION, TO_COMMAND_OPTION, TRANSFORM_OPTION, UTC_OPTION, VOLNO_FILE_OPTION, WARNING_OPTION, XATTR_OPTION, XATTR_EXCLUDE, XATTR_INCLUDE }; #define N_(s) s static char const doc[] = N_("\ GNU 'tar' saves many files together into a single tape or disk archive, \ and can restore individual files from the archive.\n\ \n\ Examples:\n\ tar -cf archive.tar foo bar # Create archive.tar from files foo and bar.\n\ tar -tvf archive.tar # List all files in archive.tar verbosely.\n\ tar -xf archive.tar # Extract all files from archive.tar.\n") "\v" N_("The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ The version control may be set with --backup or VERSION_CONTROL, values are:\n\n\ none, off never make backups\n\ t, numbered make numbered backups\n\ nil, existing numbered if numbered backups exist, simple otherwise\n\ never, simple always make simple backups\n"); static struct argp_option options[] = { #define GRID 10 {NULL, 0, NULL, 0, N_("Main operation mode:"), GRID }, {"list", 't', 0, 0, N_("list the contents of an archive"), GRID+1 }, {"extract", 'x', 0, 0, N_("extract files from an archive"), GRID+1 }, {"get", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"create", 'c', 0, 0, N_("create a new archive"), GRID+1 }, {"diff", 'd', 0, 0, N_("find differences between archive and file system"), GRID+1 }, {"compare", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"append", 'r', 0, 0, N_("append files to the end of an archive"), GRID+1 }, {"update", 'u', 0, 0, N_("only append files newer than copy in archive"), GRID+1 }, {"catenate", 'A', 0, 0, N_("append tar files to an archive"), GRID+1 }, {"concatenate", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"delete", DELETE_OPTION, 0, 0, N_("delete from the archive (not on mag tapes!)"), GRID+1 }, {"test-label", TEST_LABEL_OPTION, NULL, 0, N_("test the archive volume label and exit"), GRID+1 }, #undef GRID #define GRID 20 {NULL, 0, NULL, 0, N_("Operation modifiers:"), GRID }, {"sparse", 'S', 0, 0, N_("handle sparse files efficiently"), GRID+1 }, {"hole-detection", HOLE_DETECTION_OPTION, N_("TYPE"), 0, N_("technique to detect holes"), GRID+1 }, {"sparse-version", SPARSE_VERSION_OPTION, N_("MAJOR[.MINOR]"), 0, N_("set version of the sparse format to use (implies --sparse)"), GRID+1}, {"incremental", 'G', 0, 0, N_("handle old GNU-format incremental backup"), GRID+1 }, {"listed-incremental", 'g', N_("FILE"), 0, N_("handle new GNU-format incremental backup"), GRID+1 }, {"level", LEVEL_OPTION, N_("NUMBER"), 0, N_("dump level for created listed-incremental archive"), GRID+1 }, {"ignore-failed-read", IGNORE_FAILED_READ_OPTION, 0, 0, N_("do not exit with nonzero on unreadable files"), GRID+1 }, {"occurrence", OCCURRENCE_OPTION, N_("NUMBER"), OPTION_ARG_OPTIONAL, N_("process only the NUMBERth occurrence of each file in the archive;" " this option is valid only in conjunction with one of the subcommands" " --delete, --diff, --extract or --list and when a list of files" " is given either on the command line or via the -T option;" " NUMBER defaults to 1"), GRID+1 }, {"seek", 'n', NULL, 0, N_("archive is seekable"), GRID+1 }, {"no-seek", NO_SEEK_OPTION, NULL, 0, N_("archive is not seekable"), GRID+1 }, {"no-check-device", NO_CHECK_DEVICE_OPTION, NULL, 0, N_("do not check device numbers when creating incremental archives"), GRID+1 }, {"check-device", CHECK_DEVICE_OPTION, NULL, 0, N_("check device numbers when creating incremental archives (default)"), GRID+1 }, #undef GRID #define GRID 30 {NULL, 0, NULL, 0, N_("Overwrite control:"), GRID }, {"verify", 'W', 0, 0, N_("attempt to verify the archive after writing it"), GRID+1 }, {"remove-files", REMOVE_FILES_OPTION, 0, 0, N_("remove files after adding them to the archive"), GRID+1 }, {"keep-old-files", 'k', 0, 0, N_("don't replace existing files when extracting, " "treat them as errors"), GRID+1 }, {"skip-old-files", SKIP_OLD_FILES_OPTION, 0, 0, N_("don't replace existing files when extracting, silently skip over them"), GRID+1 }, {"keep-newer-files", KEEP_NEWER_FILES_OPTION, 0, 0, N_("don't replace existing files that are newer than their archive copies"), GRID+1 }, {"overwrite", OVERWRITE_OPTION, 0, 0, N_("overwrite existing files when extracting"), GRID+1 }, {"unlink-first", 'U', 0, 0, N_("remove each file prior to extracting over it"), GRID+1 }, {"recursive-unlink", RECURSIVE_UNLINK_OPTION, 0, 0, N_("empty hierarchies prior to extracting directory"), GRID+1 }, {"no-overwrite-dir", NO_OVERWRITE_DIR_OPTION, 0, 0, N_("preserve metadata of existing directories"), GRID+1 }, {"overwrite-dir", OVERWRITE_DIR_OPTION, 0, 0, N_("overwrite metadata of existing directories when extracting (default)"), GRID+1 }, {"keep-directory-symlink", KEEP_DIRECTORY_SYMLINK_OPTION, 0, 0, N_("preserve existing symlinks to directories when extracting"), GRID+1 }, {"one-top-level", ONE_TOP_LEVEL_OPTION, N_("DIR"), OPTION_ARG_OPTIONAL, N_("create a subdirectory to avoid having loose files extracted"), GRID+1 }, #undef GRID #define GRID 40 {NULL, 0, NULL, 0, N_("Select output stream:"), GRID }, {"to-stdout", 'O', 0, 0, N_("extract files to standard output"), GRID+1 }, {"to-command", TO_COMMAND_OPTION, N_("COMMAND"), 0, N_("pipe extracted files to another program"), GRID+1 }, {"ignore-command-error", IGNORE_COMMAND_ERROR_OPTION, 0, 0, N_("ignore exit codes of children"), GRID+1 }, {"no-ignore-command-error", NO_IGNORE_COMMAND_ERROR_OPTION, 0, 0, N_("treat non-zero exit codes of children as error"), GRID+1 }, #undef GRID #define GRID 50 {NULL, 0, NULL, 0, N_("Handling of file attributes:"), GRID }, {"owner", OWNER_OPTION, N_("NAME"), 0, N_("force NAME as owner for added files"), GRID+1 }, {"group", GROUP_OPTION, N_("NAME"), 0, N_("force NAME as group for added files"), GRID+1 }, {"owner-map", OWNER_MAP_OPTION, N_("FILE"), 0, N_("use FILE to map file owner UIDs and names"), GRID+1 }, {"group-map", GROUP_MAP_OPTION, N_("FILE"), 0, N_("use FILE to map file owner GIDs and names"), GRID+1 }, {"mtime", MTIME_OPTION, N_("DATE-OR-FILE"), 0, N_("set mtime for added files from DATE-OR-FILE"), GRID+1 }, {"mode", MODE_OPTION, N_("CHANGES"), 0, N_("force (symbolic) mode CHANGES for added files"), GRID+1 }, {"atime-preserve", ATIME_PRESERVE_OPTION, N_("METHOD"), OPTION_ARG_OPTIONAL, N_("preserve access times on dumped files, either by restoring the times" " after reading (METHOD='replace'; default) or by not setting the times" " in the first place (METHOD='system')"), GRID+1 }, {"touch", 'm', 0, 0, N_("don't extract file modified time"), GRID+1 }, {"same-owner", SAME_OWNER_OPTION, 0, 0, N_("try extracting files with the same ownership as exists in the archive (default for superuser)"), GRID+1 }, {"no-same-owner", NO_SAME_OWNER_OPTION, 0, 0, N_("extract files as yourself (default for ordinary users)"), GRID+1 }, {"numeric-owner", NUMERIC_OWNER_OPTION, 0, 0, N_("always use numbers for user/group names"), GRID+1 }, {"preserve-permissions", 'p', 0, 0, N_("extract information about file permissions (default for superuser)"), GRID+1 }, {"same-permissions", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"no-same-permissions", NO_SAME_PERMISSIONS_OPTION, 0, 0, N_("apply the user's umask when extracting permissions from the archive (default for ordinary users)"), GRID+1 }, {"preserve-order", 's', 0, 0, N_("member arguments are listed in the same order as the " "files in the archive"), GRID+1 }, {"same-order", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"preserve", PRESERVE_OPTION, 0, 0, N_("same as both -p and -s"), GRID+1 }, {"delay-directory-restore", DELAY_DIRECTORY_RESTORE_OPTION, 0, 0, N_("delay setting modification times and permissions of extracted" " directories until the end of extraction"), GRID+1 }, {"no-delay-directory-restore", NO_DELAY_DIRECTORY_RESTORE_OPTION, 0, 0, N_("cancel the effect of --delay-directory-restore option"), GRID+1 }, {"sort", SORT_OPTION, N_("ORDER"), 0, #if D_INO_IN_DIRENT N_("directory sorting order: none (default), name or inode" #else N_("directory sorting order: none (default) or name" #endif ), GRID+1 }, #undef GRID #define GRID 55 {NULL, 0, NULL, 0, N_("Handling of extended file attributes:"), GRID }, {"xattrs", XATTR_OPTION, 0, 0, N_("Enable extended attributes support"), GRID+1 }, {"no-xattrs", NO_XATTR_OPTION, 0, 0, N_("Disable extended attributes support"), GRID+1 }, {"xattrs-include", XATTR_INCLUDE, N_("MASK"), 0, N_("specify the include pattern for xattr keys"), GRID+1 }, {"xattrs-exclude", XATTR_EXCLUDE, N_("MASK"), 0, N_("specify the exclude pattern for xattr keys"), GRID+1 }, {"selinux", SELINUX_CONTEXT_OPTION, 0, 0, N_("Enable the SELinux context support"), GRID+1 }, {"no-selinux", NO_SELINUX_CONTEXT_OPTION, 0, 0, N_("Disable the SELinux context support"), GRID+1 }, {"acls", ACLS_OPTION, 0, 0, N_("Enable the POSIX ACLs support"), GRID+1 }, {"no-acls", NO_ACLS_OPTION, 0, 0, N_("Disable the POSIX ACLs support"), GRID+1 }, #undef GRID #define GRID 60 {NULL, 0, NULL, 0, N_("Device selection and switching:"), GRID }, {"file", 'f', N_("ARCHIVE"), 0, N_("use archive file or device ARCHIVE"), GRID+1 }, {"force-local", FORCE_LOCAL_OPTION, 0, 0, N_("archive file is local even if it has a colon"), GRID+1 }, {"rmt-command", RMT_COMMAND_OPTION, N_("COMMAND"), 0, N_("use given rmt COMMAND instead of rmt"), GRID+1 }, {"rsh-command", RSH_COMMAND_OPTION, N_("COMMAND"), 0, N_("use remote COMMAND instead of rsh"), GRID+1 }, #ifdef DEVICE_PREFIX {"-[0-7][lmh]", 0, NULL, OPTION_DOC, /* It is OK, since 'name' will never be translated */ N_("specify drive and density"), GRID+1 }, #endif {NULL, '0', NULL, OPTION_HIDDEN, NULL, GRID+1 }, {NULL, '1', NULL, OPTION_HIDDEN, NULL, GRID+1 }, {NULL, '2', NULL, OPTION_HIDDEN, NULL, GRID+1 }, {NULL, '3', NULL, OPTION_HIDDEN, NULL, GRID+1 }, {NULL, '4', NULL, OPTION_HIDDEN, NULL, GRID+1 }, {NULL, '5', NULL, OPTION_HIDDEN, NULL, GRID+1 }, {NULL, '6', NULL, OPTION_HIDDEN, NULL, GRID+1 }, {NULL, '7', NULL, OPTION_HIDDEN, NULL, GRID+1 }, {NULL, '8', NULL, OPTION_HIDDEN, NULL, GRID+1 }, {NULL, '9', NULL, OPTION_HIDDEN, NULL, GRID+1 }, {"multi-volume", 'M', 0, 0, N_("create/list/extract multi-volume archive"), GRID+1 }, {"tape-length", 'L', N_("NUMBER"), 0, N_("change tape after writing NUMBER x 1024 bytes"), GRID+1 }, {"info-script", 'F', N_("NAME"), 0, N_("run script at end of each tape (implies -M)"), GRID+1 }, {"new-volume-script", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"volno-file", VOLNO_FILE_OPTION, N_("FILE"), 0, N_("use/update the volume number in FILE"), GRID+1 }, #undef GRID #define GRID 70 {NULL, 0, NULL, 0, N_("Device blocking:"), GRID }, {"blocking-factor", 'b', N_("BLOCKS"), 0, N_("BLOCKS x 512 bytes per record"), GRID+1 }, {"record-size", RECORD_SIZE_OPTION, N_("NUMBER"), 0, N_("NUMBER of bytes per record, multiple of 512"), GRID+1 }, {"ignore-zeros", 'i', 0, 0, N_("ignore zeroed blocks in archive (means EOF)"), GRID+1 }, {"read-full-records", 'B', 0, 0, N_("reblock as we read (for 4.2BSD pipes)"), GRID+1 }, #undef GRID #define GRID 80 {NULL, 0, NULL, 0, N_("Archive format selection:"), GRID }, {"format", 'H', N_("FORMAT"), 0, N_("create archive of the given format"), GRID+1 }, {NULL, 0, NULL, 0, N_("FORMAT is one of the following:"), GRID+2 }, {" v7", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("old V7 tar format"), GRID+3 }, {" oldgnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("GNU format as per tar <= 1.12"), GRID+3 }, {" gnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("GNU tar 1.13.x format"), GRID+3 }, {" ustar", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("POSIX 1003.1-1988 (ustar) format"), GRID+3 }, {" pax", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("POSIX 1003.1-2001 (pax) format"), GRID+3 }, {" posix", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("same as pax"), GRID+3 }, {"old-archive", OLD_ARCHIVE_OPTION, 0, 0, /* FIXME */ N_("same as --format=v7"), GRID+8 }, {"portability", 0, 0, OPTION_ALIAS, NULL, GRID+8 }, {"posix", POSIX_OPTION, 0, 0, N_("same as --format=posix"), GRID+8 }, {"pax-option", PAX_OPTION, N_("keyword[[:]=value][,keyword[[:]=value]]..."), 0, N_("control pax keywords"), GRID+8 }, {"label", 'V', N_("TEXT"), 0, N_("create archive with volume name TEXT; at list/extract time, use TEXT as a globbing pattern for volume name"), GRID+8 }, #undef GRID #define GRID 90 {NULL, 0, NULL, 0, N_("Compression options:"), GRID }, {"auto-compress", 'a', 0, 0, N_("use archive suffix to determine the compression program"), GRID+1 }, {"no-auto-compress", NO_AUTO_COMPRESS_OPTION, 0, 0, N_("do not use archive suffix to determine the compression program"), GRID+1 }, {"use-compress-program", 'I', N_("PROG"), 0, N_("filter through PROG (must accept -d)"), GRID+1 }, /* Note: docstrings for the options below are generated by tar_help_filter */ {"bzip2", 'j', 0, 0, NULL, GRID+1 }, {"gzip", 'z', 0, 0, NULL, GRID+1 }, {"gunzip", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"ungzip", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"compress", 'Z', 0, 0, NULL, GRID+1 }, {"uncompress", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"lzip", LZIP_OPTION, 0, 0, NULL, GRID+1 }, {"lzma", LZMA_OPTION, 0, 0, NULL, GRID+1 }, {"lzop", LZOP_OPTION, 0, 0, NULL, GRID+1 }, {"xz", 'J', 0, 0, NULL, GRID+1 }, #undef GRID #define GRID 100 {NULL, 0, NULL, 0, N_("Local file selection:"), GRID }, {"one-file-system", ONE_FILE_SYSTEM_OPTION, 0, 0, N_("stay in local file system when creating archive"), GRID+1 }, {"absolute-names", 'P', 0, 0, N_("don't strip leading '/'s from file names"), GRID+1 }, {"dereference", 'h', 0, 0, N_("follow symlinks; archive and dump the files they point to"), GRID+1 }, {"hard-dereference", HARD_DEREFERENCE_OPTION, 0, 0, N_("follow hard links; archive and dump the files they refer to"), GRID+1 }, {"starting-file", 'K', N_("MEMBER-NAME"), 0, N_("begin at member MEMBER-NAME when reading the archive"), GRID+1 }, {"newer", 'N', N_("DATE-OR-FILE"), 0, N_("only store files newer than DATE-OR-FILE"), GRID+1 }, {"after-date", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"newer-mtime", NEWER_MTIME_OPTION, N_("DATE"), 0, N_("compare date and time when data changed only"), GRID+1 }, {"backup", BACKUP_OPTION, N_("CONTROL"), OPTION_ARG_OPTIONAL, N_("backup before removal, choose version CONTROL"), GRID+1 }, {"suffix", SUFFIX_OPTION, N_("STRING"), 0, N_("backup before removal, override usual suffix ('~' unless overridden by environment variable SIMPLE_BACKUP_SUFFIX)"), GRID+1 }, #undef GRID #define GRID 110 {NULL, 0, NULL, 0, N_("File name transformations:"), GRID }, {"strip-components", STRIP_COMPONENTS_OPTION, N_("NUMBER"), 0, N_("strip NUMBER leading components from file names on extraction"), GRID+1 }, {"transform", TRANSFORM_OPTION, N_("EXPRESSION"), 0, N_("use sed replace EXPRESSION to transform file names"), GRID+1 }, {"xform", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, #undef GRID #define GRID 130 {NULL, 0, NULL, 0, N_("Informative output:"), GRID }, {"verbose", 'v', 0, 0, N_("verbosely list files processed"), GRID+1 }, {"warning", WARNING_OPTION, N_("KEYWORD"), 0, N_("warning control"), GRID+1 }, {"checkpoint", CHECKPOINT_OPTION, N_("NUMBER"), OPTION_ARG_OPTIONAL, N_("display progress messages every NUMBERth record (default 10)"), GRID+1 }, {"checkpoint-action", CHECKPOINT_ACTION_OPTION, N_("ACTION"), 0, N_("execute ACTION on each checkpoint"), GRID+1 }, {"check-links", 'l', 0, 0, N_("print a message if not all links are dumped"), GRID+1 }, {"totals", TOTALS_OPTION, N_("SIGNAL"), OPTION_ARG_OPTIONAL, N_("print total bytes after processing the archive; " "with an argument - print total bytes when this SIGNAL is delivered; " "Allowed signals are: SIGHUP, SIGQUIT, SIGINT, SIGUSR1 and SIGUSR2; " "the names without SIG prefix are also accepted"), GRID+1 }, {"utc", UTC_OPTION, 0, 0, N_("print file modification times in UTC"), GRID+1 }, {"full-time", FULL_TIME_OPTION, 0, 0, N_("print file time to its full resolution"), GRID+1 }, {"index-file", INDEX_FILE_OPTION, N_("FILE"), 0, N_("send verbose output to FILE"), GRID+1 }, {"block-number", 'R', 0, 0, N_("show block number within archive with each message"), GRID+1 }, {"interactive", 'w', 0, 0, N_("ask for confirmation for every action"), GRID+1 }, {"confirmation", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"show-defaults", SHOW_DEFAULTS_OPTION, 0, 0, N_("show tar defaults"), GRID+1 }, {"show-snapshot-field-ranges", SHOW_SNAPSHOT_FIELD_RANGES_OPTION, 0, 0, N_("show valid ranges for snapshot-file fields"), GRID+1 }, {"show-omitted-dirs", SHOW_OMITTED_DIRS_OPTION, 0, 0, N_("when listing or extracting, list each directory that does not match search criteria"), GRID+1 }, {"show-transformed-names", SHOW_TRANSFORMED_NAMES_OPTION, 0, 0, N_("show file or archive names after transformation"), GRID+1 }, {"show-stored-names", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, {"quoting-style", QUOTING_STYLE_OPTION, N_("STYLE"), 0, N_("set name quoting style; see below for valid STYLE values"), GRID+1 }, {"quote-chars", QUOTE_CHARS_OPTION, N_("STRING"), 0, N_("additionally quote characters from STRING"), GRID+1 }, {"no-quote-chars", NO_QUOTE_CHARS_OPTION, N_("STRING"), 0, N_("disable quoting for characters from STRING"), GRID+1 }, #undef GRID #define GRID 140 {NULL, 0, NULL, 0, N_("Compatibility options:"), GRID }, {NULL, 'o', 0, 0, N_("when creating, same as --old-archive; when extracting, same as --no-same-owner"), GRID+1 }, #undef GRID #define GRID 150 {NULL, 0, NULL, 0, N_("Other options:"), GRID }, {"restrict", RESTRICT_OPTION, 0, 0, N_("disable use of some potentially harmful options"), -1 }, #undef GRID {0, 0, 0, 0, 0, 0} }; static struct argp argp = { options, NULL, N_("[FILE]..."), doc }; int main (int argc, char **argv) { argp_parse(&argp, argc, argv, 0, 0, 0); return 0; }
From 211cc9a27148a2c978886cc508aa44d0fccf54a7 Mon Sep 17 00:00:00 2001 From: Simon Reinhardt <simon.reinha...@stud.uni-regensburg.de> Date: Sun, 7 Feb 2016 22:39:07 +0100 Subject: [PATCH] argp: Fix line wrapping for '--help' output Let argp_fmtstream_update flush the format-streams's buffer as soon as possible. This avoids formatting errors once the buffer is full. Add tests for this. --- ChangeLog | 13 ++++ lib/argp-fmtstream.c | 203 +++++++++++++-------------------------------------- lib/argp-fmtstream.h | 12 ++- lib/argp-help.c | 3 + tests/test-argp-2.sh | 18 +++-- tests/test-argp.c | 5 ++ 6 files changed, 91 insertions(+), 163 deletions(-) diff --git a/ChangeLog b/ChangeLog index 40bdd23..38ec2ed 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2016-02-09 Simon Reinhardt + + argp: fix line wrapping of '--help' output + * lib/argp-fmtstream.c (__argp_fmtstream_update): + Flush output as soon as possible. + * lib/argp-fmtstream.h (struct argp_fmtstream): + Member point_offs is no longer needed. + * lib/argp-help.c (indent_to): + Flush output to avoid a spurious newline before an overlong word. + * tests/test-argp.c (group2_1_option): + * test/test-argp-2.sh: + Add tests. + 2016-02-08 Paul Eggert <egg...@cs.ucla.edu> readdir_r: now obsolescent diff --git a/lib/argp-fmtstream.c b/lib/argp-fmtstream.c index 16a706c..26d06b6 100644 --- a/lib/argp-fmtstream.c +++ b/lib/argp-fmtstream.c @@ -29,6 +29,7 @@ #include <errno.h> #include <stdarg.h> #include <ctype.h> +#include <assert.h> #include "argp-fmtstream.h" #include "argp-namefrob.h" @@ -69,7 +70,6 @@ __argp_make_fmtstream (FILE *stream, fs->rmargin = rmargin; fs->wmargin = wmargin; fs->point_col = 0; - fs->point_offs = 0; fs->buf = (char *) malloc (INIT_BUF_SIZE); if (! fs->buf) @@ -116,8 +116,19 @@ weak_alias (__argp_fmtstream_free, argp_fmtstream_free) #endif #endif -/* Process FS's buffer so that line wrapping is done from POINT_OFFS to the - end of its buffer. This code is mostly from glibc stdio/linewrap.c. */ + +static void +write_block (argp_fmtstream_t fs, char *buf, int len) +{ +#ifdef _LIBC + __fxprintf (fs->stream, "%.*s", len, buf); +#else + fwrite_unlocked (buf, 1, len, fs->stream); +#endif +} + +/* Process FS's buffer so that line wrapping is done and flush all of it. So + after return fs->p will be set to fb->buf. */ void __argp_fmtstream_update (argp_fmtstream_t fs) { @@ -125,7 +136,7 @@ __argp_fmtstream_update (argp_fmtstream_t fs) size_t len; /* Scan the buffer for newlines. */ - buf = fs->buf + fs->point_offs; + buf = fs->buf; while (buf < fs->p) { size_t r; @@ -133,31 +144,10 @@ __argp_fmtstream_update (argp_fmtstream_t fs) if (fs->point_col == 0 && fs->lmargin != 0) { /* We are starting a new line. Print spaces to the left margin. */ - const size_t pad = fs->lmargin; - if (fs->p + pad < fs->end) - { - /* We can fit in them in the buffer by moving the - buffer text up and filling in the beginning. */ - memmove (buf + pad, buf, fs->p - buf); - fs->p += pad; /* Compensate for bigger buffer. */ - memset (buf, ' ', pad); /* Fill in the spaces. */ - buf += pad; /* Don't bother searching them. */ - } - else - { - /* No buffer space for spaces. Must flush. */ - size_t i; - for (i = 0; i < pad; i++) - { -#ifdef USE_IN_LIBIO - if (_IO_fwide (fs->stream, 0) > 0) - putwc_unlocked (L' ', fs->stream); - else -#endif - putc_unlocked (' ', fs->stream); - } - } - fs->point_col = pad; + size_t i; + for (i = 0; i < fs->lmargin; i++) + write_block(fs, " ", 1); + fs->point_col = fs->lmargin; } len = fs->p - buf; @@ -173,8 +163,9 @@ __argp_fmtstream_update (argp_fmtstream_t fs) if (fs->point_col + len < fs->rmargin) { /* The remaining buffer text is a partial line and fits - within the maximum line width. Advance point for the - characters to be written and stop scanning. */ + within the maximum line width. Output the line and increment + point. */ + write_block(fs, buf, len); fs->point_col += len; break; } @@ -186,7 +177,9 @@ __argp_fmtstream_update (argp_fmtstream_t fs) else if (fs->point_col + (nl - buf) < (ssize_t) fs->rmargin) { /* The buffer contains a full line that fits within the maximum - line width. Reset point and scan the next line. */ + line width. Output the line, reset point and scan the next + line. */ + write_block(fs, buf, nl + 1 - buf); fs->point_col = 0; buf = nl + 1; continue; @@ -197,23 +190,24 @@ __argp_fmtstream_update (argp_fmtstream_t fs) if (fs->wmargin < 0) { - /* Truncate the line by overwriting the excess with the - newline and anything after it in the buffer. */ + /* Truncated everything past the right margin. */ if (nl < fs->p) { - memmove (buf + (r - fs->point_col), nl, fs->p - nl); - fs->p -= buf + (r - fs->point_col) - nl; + write_block(fs, buf, r - fs->point_col); + write_block(fs, "\n", 1); /* Reset point for the next line and start scanning it. */ fs->point_col = 0; - buf += r + 1; /* Skip full line plus \n. */ + buf = nl + 1; /* Skip full line plus \n. */ + continue; } else { + assert(nl == fs->p); /* The buffer ends with a partial line that is beyond the maximum line width. Advance point for the characters written, and discard those past the max from the buffer. */ - fs->point_col += len; - fs->p -= fs->point_col - r; + write_block(fs, buf, r - fs->point_col); + fs->point_col += len; break; } } @@ -250,99 +244,26 @@ __argp_fmtstream_update (argp_fmtstream_t fs) do ++p; while (p < nl && !isblank ((unsigned char) *p)); - if (p == nl) - { - /* It already ends a line. No fussing required. */ - fs->point_col = 0; - buf = nl + 1; - continue; - } - /* We will move the newline to replace the first blank. */ - nl = p; - /* Swallow separating blanks. */ - do - ++p; - while (isblank ((unsigned char) *p)); - /* The next line will start here. */ - nextline = p; - } - - /* Note: There are a bunch of tests below for - NEXTLINE == BUF + LEN + 1; this case is where NL happens to fall - at the end of the buffer, and NEXTLINE is in fact empty (and so - we need not be careful to maintain its contents). */ - - if ((nextline == buf + len + 1 - ? fs->end - nl < fs->wmargin + 1 - : nextline - (nl + 1) < fs->wmargin) - && fs->p > nextline) - { - /* The margin needs more blanks than we removed. */ - if (fs->end - fs->p > fs->wmargin + 1) - /* Make some space for them. */ - { - size_t mv = fs->p - nextline; - memmove (nl + 1 + fs->wmargin, nextline, mv); - nextline = nl + 1 + fs->wmargin; - len = nextline + mv - buf; - *nl++ = '\n'; - } - else - /* Output the first line so we can use the space. */ - { -#ifdef _LIBC - __fxprintf (fs->stream, "%.*s\n", - (int) (nl - fs->buf), fs->buf); -#else - if (nl > fs->buf) - fwrite_unlocked (fs->buf, 1, nl - fs->buf, fs->stream); - putc_unlocked ('\n', fs->stream); -#endif - - len += buf - fs->buf; - nl = buf = fs->buf; - } - } - else - /* We can fit the newline and blanks in before - the next word. */ - *nl++ = '\n'; - - if (nextline - nl >= fs->wmargin - || (nextline == buf + len + 1 && fs->end - nextline >= fs->wmargin)) - /* Add blanks up to the wrap margin column. */ - for (i = 0; i < fs->wmargin; ++i) - *nl++ = ' '; - else - for (i = 0; i < fs->wmargin; ++i) -#ifdef USE_IN_LIBIO - if (_IO_fwide (fs->stream, 0) > 0) - putwc_unlocked (L' ', fs->stream); - else -#endif - putc_unlocked (' ', fs->stream); - - /* Copy the tail of the original buffer into the current buffer - position. */ - if (nl < nextline) - memmove (nl, nextline, buf + len - nextline); - len -= nextline - buf; - - /* Continue the scan on the remaining lines in the buffer. */ - buf = nl; - - /* Restore bufp to include all the remaining text. */ - fs->p = nl + len; - - /* Reset the counter of what has been output this line. If wmargin - is 0, we want to avoid the lmargin getting added, so we set - point_col to a magic value of -1 in that case. */ - fs->point_col = fs->wmargin ? fs->wmargin : -1; - } + nl = p; + nextline = nl + 1; + } + + write_block(fs, buf, nl - buf); + if (nextline < fs->p) + { + /* There are more lines to process. Do line break and print + blanks up to the wrap margin. */ + write_block(fs, "\n", 1); + for (i = 0; i < fs->wmargin; ++i) + write_block(fs, " ", 1); + fs->point_col = fs->wmargin; + } + buf = nextline; + } } - /* Remember that we've scanned as far as the end of the buffer. */ - fs->point_offs = fs->p - fs->buf; + /* Remember that we've flushed everything. */ + fs->p = fs->buf; } /* Ensure that FS has space for AMOUNT more bytes in its buffer, either by @@ -352,29 +273,9 @@ __argp_fmtstream_ensure (struct argp_fmtstream *fs, size_t amount) { if ((size_t) (fs->end - fs->p) < amount) { - ssize_t wrote; - /* Flush FS's buffer. */ __argp_fmtstream_update (fs); - -#ifdef _LIBC - __fxprintf (fs->stream, "%.*s", (int) (fs->p - fs->buf), fs->buf); - wrote = fs->p - fs->buf; -#else - wrote = fwrite_unlocked (fs->buf, 1, fs->p - fs->buf, fs->stream); -#endif - if (wrote == fs->p - fs->buf) - { - fs->p = fs->buf; - fs->point_offs = 0; - } - else - { - fs->p -= wrote; - fs->point_offs -= wrote; - memmove (fs->buf, fs->buf + wrote, fs->p - fs->buf); - return 0; - } + assert(fs->p == fs->buf); if ((size_t) (fs->end - fs->buf) < amount) /* Gotta grow the buffer. */ diff --git a/lib/argp-fmtstream.h b/lib/argp-fmtstream.h index e5dfade..b85e088 100644 --- a/lib/argp-fmtstream.h +++ b/lib/argp-fmtstream.h @@ -95,9 +95,7 @@ struct argp_fmtstream size_t lmargin, rmargin; /* Left and right margins. */ ssize_t wmargin; /* Margin to wrap to, or -1 to truncate. */ - /* Point in buffer to which we've processed for wrapping, but not output. */ - size_t point_offs; - /* Output column at POINT_OFFS, or -1 meaning 0 but don't add lmargin. */ + /* Output column at buf, or -1 meaning 0 but don't add lmargin. */ ssize_t point_col; char *buf; /* Output buffer. */ @@ -302,7 +300,7 @@ ARGP_FS_EI size_t __argp_fmtstream_set_lmargin (argp_fmtstream_t __fs, size_t __lmargin) { size_t __old; - if ((size_t) (__fs->p - __fs->buf) > __fs->point_offs) + if (__fs->p > __fs->buf) __argp_fmtstream_update (__fs); __old = __fs->lmargin; __fs->lmargin = __lmargin; @@ -314,7 +312,7 @@ ARGP_FS_EI size_t __argp_fmtstream_set_rmargin (argp_fmtstream_t __fs, size_t __rmargin) { size_t __old; - if ((size_t) (__fs->p - __fs->buf) > __fs->point_offs) + if (__fs->p > __fs->buf) __argp_fmtstream_update (__fs); __old = __fs->rmargin; __fs->rmargin = __rmargin; @@ -326,7 +324,7 @@ ARGP_FS_EI size_t __argp_fmtstream_set_wmargin (argp_fmtstream_t __fs, size_t __wmargin) { size_t __old; - if ((size_t) (__fs->p - __fs->buf) > __fs->point_offs) + if (__fs->p > __fs->buf) __argp_fmtstream_update (__fs); __old = __fs->wmargin; __fs->wmargin = __wmargin; @@ -337,7 +335,7 @@ __argp_fmtstream_set_wmargin (argp_fmtstream_t __fs, size_t __wmargin) ARGP_FS_EI size_t __argp_fmtstream_point (argp_fmtstream_t __fs) { - if ((size_t) (__fs->p - __fs->buf) > __fs->point_offs) + if (__fs->p > __fs->buf) __argp_fmtstream_update (__fs); return __fs->point_col >= 0 ? __fs->point_col : 0; } diff --git a/lib/argp-help.c b/lib/argp-help.c index 2afc91b..6bcb1ca 100644 --- a/lib/argp-help.c +++ b/lib/argp-help.c @@ -943,6 +943,9 @@ indent_to (argp_fmtstream_t stream, unsigned col) int needed = col - __argp_fmtstream_point (stream); while (needed-- > 0) __argp_fmtstream_putc (stream, ' '); + /* Flush stream to avoid spurious newline before overlong word + (see argp-test.c). */ + __argp_fmtstream_update(stream); } /* Output to STREAM either a space, or a newline if there isn't room for at diff --git a/tests/test-argp-2.sh b/tests/test-argp-2.sh index 406dbc0..5fdb78f 100755 --- a/tests/test-argp-2.sh +++ b/tests/test-argp-2.sh @@ -33,10 +33,10 @@ func_compare() { #### # Test --usage output cat > $TMP <<EOT -Usage: test-argp [-tvCSOlp?V] [-f FILE] [-r FILE] [-o[ARG]] [--test] +Usage: test-argp [-tvCSOdlp?V] [-f FILE] [-r FILE] [-o[ARG]] [--test] [--file=FILE] [--input=FILE] [--read=FILE] [--verbose] [--cantiga] - [--sonet] [--option] [--optional[=ARG]] [--limerick] [--poem] - [--help] [--usage] [--version] ARGS... + [--sonet] [--option] [--optional[=ARG]] [--dada] [--limerick] + [--poem] [--help] [--usage] [--version] ARGS... EOT ./test-argp$EXEEXT --usage | func_compare || ERR=1 @@ -45,9 +45,9 @@ EOT # Test working usage-indent format cat > $TMP <<EOT -Usage: test-argp [-tvCSOlp?V] [-f FILE] [-r FILE] [-o[ARG]] [--test] +Usage: test-argp [-tvCSOdlp?V] [-f FILE] [-r FILE] [-o[ARG]] [--test] [--file=FILE] [--input=FILE] [--read=FILE] [--verbose] [--cantiga] [--sonet] -[--option] [--optional[=ARG]] [--limerick] [--poem] [--help] [--usage] +[--option] [--optional[=ARG]] [--dada] [--limerick] [--poem] [--help] [--usage] [--version] ARGS... EOT @@ -82,8 +82,16 @@ documentation string two two units Option Group 2.1 + -d, --dada . + . + . + . + . + . + . -l, --limerick create a limerick -p, --poem create a poem + ............................................................................................. -?, --help give this help list --usage give a short usage message diff --git a/tests/test-argp.c b/tests/test-argp.c index d786953..b006b40 100644 --- a/tests/test-argp.c +++ b/tests/test-argp.c @@ -178,6 +178,11 @@ static struct argp_option group2_1_option[] = { { NULL, 0, NULL, 0, "Option Group 2.1", 0 }, { "poem", 'p', NULL, 0, "create a poem" }, { "limerick", 'l', NULL, 0, "create a limerick" }, + { "dada", 'd', NULL, 0, ".\n.\n.\n.\n.\n.\n."}, + + /* Check for spurious newline before overlong word. */ + { "........................................................................." + "....................", 0, NULL, OPTION_DOC }, { NULL, 0, NULL, 0, NULL, 0 } }; -- 2.1.4