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 <[email protected]>
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 <[email protected]>
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