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

Reply via email to