Commit 06c13b34 06c13b3498a236a518df76bdb1b9895540c1df3b by Sergey Poznyakoff

Implement simple word-wrapping stream

* include/mailutils/stream.h (MU_IOCTL_WORDWRAPSTREAM): New ioctl.
(mu_wordwrap_stream_create): New proto.
* libmailutils/stream/Makefile.am: add wordwrap.c
* libmailutils/stream/wordwrap.c: New file.
* libmailutils/tests/Makefile.am: Add new testcases.
* libmailutils/tests/testsuite.at: Include new testcases.
* libmailutils/tests/wordwrap.c: New file.
* libmailutils/tests/wordwrap00.at: New file.
* libmailutils/tests/wordwrap01.at: New file.
* libmailutils/tests/wordwrap02.at: New file.
* libmailutils/tests/wordwrap03.at: New file.
1 parent d4a938f6
......@@ -78,6 +78,7 @@ enum mu_buffer_type
always returns the topmost substream.
*/
#define MU_IOCTL_TLSSTREAM 13 /* TLS stream */
#define MU_IOCTL_WORDWRAPSTREAM 14 /* Word-wrapper stream */
/* Opcodes common for various families */
#define MU_IOCTL_OP_GET 0
......@@ -206,6 +207,11 @@ enum mu_buffer_type
#define MU_TRANSPORT_VALID_TYPE(n) \
((n) == MU_TRANSPORT_INPUT || (n) == MU_TRANSPORT_OUTPUT)
/* Word wrapper streams */
#define MU_IOCTL_WORDWRAP_GET_MARGIN 0
#define MU_IOCTL_WORDWRAP_SET_MARGIN 1
#define MU_IOCTL_WORDWRAP_MOVE_MARGIN 2
struct mu_nullstream_pattern
{
char *pattern;
......@@ -361,6 +367,9 @@ int mu_rdcache_stream_create (mu_stream_t *pstream, mu_stream_t transport,
int mu_nullstream_create (mu_stream_t *pref, int flags);
int mu_wordwrap_stream_create (mu_stream_t *pstream, mu_stream_t transport,
size_t left_margin, size_t right_margin);
#ifdef __cplusplus
}
#endif
......
......@@ -38,6 +38,7 @@ libstream_la_SOURCES = \
syslogstream.c\
tcp.c\
temp_file_stream.c\
wordwrap.c\
xscript-stream.c
AM_CPPFLAGS = @MU_LIB_COMMON_INCLUDES@ -I/libmailutils
......
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2016 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <mailutils/types.h>
#include <mailutils/stream.h>
#include <mailutils/sys/stream.h>
#include <mailutils/alloc.h>
#include <mailutils/error.h>
#include <mailutils/errno.h>
#include <mailutils/nls.h>
#include <mailutils/cctype.h>
struct mu_wordwrap_stream
{
struct _mu_stream stream;
unsigned left_margin;
unsigned right_margin;
char *buffer;
unsigned offset;
mu_stream_t transport;
};
static int
is_word (int c)
{
return !mu_isspace (c);
}
static int
full_write (struct mu_wordwrap_stream *str, size_t length)
{
size_t n, rdsize;
for (n = 0; n < length; )
{
int rc = mu_stream_write (str->transport,
str->buffer + n, length - n,
&rdsize);
if (rc)
return rc;
n += rdsize;
}
return 0;
}
static int
_wordwrap_flush_line (struct mu_wordwrap_stream *str, int lookahead)
{
size_t length, word_start, word_len = 0;
int nl = 0;
char savech;
int rc;
length = word_start = str->offset;
if (str->offset > 0 && lookahead)
{
if (is_word (str->buffer[str->offset - 1]) && is_word (lookahead))
{
/* Find the nearest word boundary */
for (length = str->offset; length > str->left_margin; length--)
{
if (!is_word (str->buffer[length - 1]))
break;
}
if (length == str->left_margin)
{
rc = full_write (str, str->offset);
if (rc == 0)
str->offset = 0;
return rc;
}
word_start = length;
}
}
while (length > 0 && mu_isblank (str->buffer[length - 1]))
length--;
if (length == 0 || str->buffer[length - 1] != '\n')
{
savech = str->buffer[length];
str->buffer[length] = '\n';
nl = 1;
}
else
nl = 0;
/* Flush the line buffer content */
rc = full_write (str, length + nl);
if (rc)
/* FIXME: Inconsistent state after error */
return rc;
if (nl)
str->buffer[length] = savech;
/* Adjust the buffer */
memset (str->buffer, ' ', str->left_margin);
if (word_start > str->left_margin)
{
word_len = str->offset - word_start;
if (word_len)
memmove (str->buffer + str->left_margin, str->buffer + word_start,
word_len);
}
str->offset = str->left_margin + word_len;
return 0;
}
static int
_wordwrap_write (mu_stream_t stream, const char *iptr, size_t isize,
size_t *nbytes)
{
struct mu_wordwrap_stream *str = (struct mu_wordwrap_stream *) stream;
size_t n;
for (n = 0; n < isize; n++)
{
if (str->offset == str->right_margin
|| (str->offset > 0 && str->buffer[str->offset - 1] == '\n'))
_wordwrap_flush_line (str, iptr[n]);
if (str->offset == str->left_margin && mu_isblank (iptr[n]))
continue;
str->buffer[str->offset++] = iptr[n];
}
if (nbytes)
*nbytes = isize;
return 0;
}
static int
_wordwrap_flush (mu_stream_t stream)
{
struct mu_wordwrap_stream *str = (struct mu_wordwrap_stream *)stream;
if (str->offset)
_wordwrap_flush_line (str, 0);
return mu_stream_flush (str->transport);
}
static void
_wordwrap_done (mu_stream_t stream)
{
struct mu_wordwrap_stream *str = (struct mu_wordwrap_stream *)stream;
mu_stream_destroy (&str->transport);
}
static int
_wordwrap_close (mu_stream_t stream)
{
struct mu_wordwrap_stream *str = (struct mu_wordwrap_stream *)stream;
return mu_stream_close (str->transport);
}
static int
set_margin (mu_stream_t stream, unsigned lmargin, int off)
{
struct mu_wordwrap_stream *str = (struct mu_wordwrap_stream *)stream;
if (off < 0 && -off > str->left_margin)
return EINVAL;
lmargin += off;
if (lmargin >= str->right_margin)
return EINVAL;
if (lmargin < str->offset)
{
str->left_margin = lmargin;
_wordwrap_flush (stream);
}
else if (lmargin > str->offset)
{
memset (str->buffer + str->offset, ' ',
lmargin - str->offset);
str->left_margin = lmargin;
str->offset = lmargin;
}
return 0;
}
static int
_wordwrap_ctl (mu_stream_t stream, int code, int opcode, void *arg)
{
struct mu_wordwrap_stream *str = (struct mu_wordwrap_stream *)stream;
int status;
switch (code)
{
case MU_IOCTL_WORDWRAPSTREAM:
switch (opcode)
{
case MU_IOCTL_WORDWRAP_GET_MARGIN:
/* Get left margin */
if (!arg)
return MU_ERR_OUT_PTR_NULL;
*(unsigned *)arg = str->left_margin;
break;
case MU_IOCTL_WORDWRAP_SET_MARGIN:
/* Set left margin */
if (!arg)
return EINVAL;
else
return set_margin (stream, *(unsigned*)arg, 0);
case MU_IOCTL_WORDWRAP_MOVE_MARGIN:
if (!arg)
return EINVAL;
else
return set_margin (stream, str->offset, *(int*)arg);
default:
return EINVAL;
}
break;
case MU_IOCTL_TRANSPORT:
if (!arg)
return EINVAL;
else
{
mu_transport_t *ptrans = arg;
switch (opcode)
{
case MU_IOCTL_OP_GET:
ptrans[0] = (mu_transport_t) str->transport;
ptrans[1] = NULL;
break;
case MU_IOCTL_OP_SET:
ptrans = arg;
if (ptrans[0])
str->transport = (mu_stream_t) ptrans[0];
break;
default:
return EINVAL;
}
}
break;
case MU_IOCTL_SUBSTREAM:
if (str->transport &&
((status = mu_stream_ioctl (str->transport, code, opcode, arg)) == 0 ||
status != ENOSYS))
return status;
/* fall through */
case MU_IOCTL_TOPSTREAM:
if (!arg)
return EINVAL;
else
{
mu_stream_t *pstr = arg;
switch (opcode)
{
case MU_IOCTL_OP_GET:
pstr[0] = str->transport;
mu_stream_ref (pstr[0]);
pstr[1] = NULL;
break;
case MU_IOCTL_OP_SET:
mu_stream_unref (str->transport);
str->transport = pstr[0];
mu_stream_ref (str->transport);
break;
default:
return EINVAL;
}
}
break;
case MU_IOCTL_FILTER:
return mu_stream_ioctl (str->transport, code, opcode, arg);
default:
return ENOSYS;
}
return 0;
}
int
mu_wordwrap_stream_create (mu_stream_t *pstream, mu_stream_t transport,
size_t left_margin, size_t right_margin)
{
int rc;
mu_stream_t stream;
struct mu_wordwrap_stream *str;
if (right_margin == 0 || left_margin >= right_margin)
return EINVAL;
str = (struct mu_wordwrap_stream *)
_mu_stream_create (sizeof (*str), MU_STREAM_APPEND);
if (!str)
return ENOMEM;
str->stream.close = _wordwrap_close;
str->stream.write = _wordwrap_write;
// str->stream.size = _wordwrap_size;
str->stream.done = _wordwrap_done;
str->stream.flush = _wordwrap_flush;
str->stream.ctl = _wordwrap_ctl;
str->transport = transport;
mu_stream_ref (transport);
str->left_margin = left_margin;
str->right_margin = right_margin;
str->buffer = mu_alloc (str->right_margin+1);
memset (str->buffer, ' ', str->left_margin);
str->offset = str->left_margin;
stream = (mu_stream_t) str;
rc = mu_stream_open (stream);
if (rc)
mu_stream_destroy (&stream);
else
*pstream = stream;
return rc;
}
......@@ -68,6 +68,7 @@ noinst_PROGRAMS = \
url-comp\
url-parse\
wicket\
wordwrap\
wsp
LDADD = ${MU_LIB_MAILUTILS}
......@@ -155,7 +156,11 @@ TESTSUITE_AT = \
url-comp.at\
xml.at\
wicket.at\
wordsplit.at
wordsplit.at\
wordwrap00.at\
wordwrap01.at\
wordwrap02.at\
wordwrap03.at
TESTSUITE = $(srcdir)/testsuite
M4=m4
......
......@@ -56,6 +56,12 @@ AT_INIT
AT_BANNER([Conversions])
m4_include([strtoc.at])
AT_BANNER([Word wrapper])
m4_include([wordwrap00.at])
m4_include([wordwrap01.at])
m4_include([wordwrap02.at])
m4_include([wordwrap03.at])
AT_BANNER([Command line parser])
m4_define([PARSEOPT_DEFAULT],[
unset ARGP_HELP_FMT
......
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2011-2012, 2014-2016 Free Software Foundation, Inc.
GNU Mailutils is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Mailutils is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <mailutils/stream.h>
#include <mailutils/stdstream.h>
#include <mailutils/diag.h>
#include <mailutils/debug.h>
#include <mailutils/errno.h>
static void
usage (FILE *fp)
{
fprintf (fp,
"usage: %s [-r N] [-l [+-]N] FILE [[-l [+-]N] FILE...]\n",
mu_program_name);
fprintf (fp, "\n");
fprintf (fp, " -l N set left margin\n");
fprintf (fp, " -l +N move left margin N chars to the right from the current position\n");
fprintf (fp, " -l -N move left margin N chars to the left from the current position\n");
fprintf (fp, " -r N set right margin\n");
fprintf (fp, " -h, -? display this help\n");
}
int
main (int argc, char **argv)
{
unsigned left = 0, right = 80;
mu_stream_t str;
int i;
mu_set_program_name (argv[0]);
for (i = 1; i < argc; i++)
{
char *arg = argv[i];
if (strncmp (arg, "-l", 2) == 0)
{
if (arg[2] == 0)
{
++i;
left = strtoul (argv[i], NULL, 10);
}
else
{
left = strtoul (argv[i] + 2, NULL, 10);
}
}
else if (strncmp (arg, "-r", 2) == 0)
{
if (arg[2] == 0)
{
++i;
right = strtoul (argv[i], NULL, 10);
}
else
{
right = strtoul (argv[i] + 2, NULL, 10);
}
}
else if (strcmp (arg, "--") == 0)
{
i++;
break;
}
else if (arg[0] == '-')
{
if (arg[1] == 0)
break;
else if ((arg[1] == 'h' || arg[1] == '?') && arg[2] == 0)
{
usage (stdout);
return 0;
}
else
{
fprintf (stderr, "%s: unrecognized argument %s\n",
mu_program_name, arg);
usage (stderr);
return 1;
}
}
else
break;
}
if (i == argc)
{
fprintf (stderr, "%s: no files\n", mu_program_name);
usage (stderr);
return 1;
}
mu_stdstream_setup (MU_STDSTREAM_RESET_NONE);
MU_ASSERT (mu_wordwrap_stream_create (&str, mu_strout, left, right));
for (; i < argc; i++)
{
char *arg = argv[i];
if (strncmp (arg, "-l", 2) == 0)
{
if (arg[2] == 0)
{
++i;
arg = argv[i];
}
else
arg += 2;
if (arg[0] == '+' || arg[0] == '-')
{
int off = strtol (arg, NULL, 10);
MU_ASSERT (mu_stream_ioctl (str, MU_IOCTL_WORDWRAPSTREAM,
MU_IOCTL_WORDWRAP_MOVE_MARGIN,
&off));
}
else
{
left = strtoul (arg, NULL, 10);
MU_ASSERT (mu_stream_ioctl (str, MU_IOCTL_WORDWRAPSTREAM,
MU_IOCTL_WORDWRAP_SET_MARGIN,
&left));
}
}
else if (arg[0] == '-' && arg[1] == 0)
{
MU_ASSERT (mu_stream_copy (str, mu_strin, 0, NULL));
mu_stream_close (mu_strin);
}
else
{
mu_stream_t in;
MU_ASSERT (mu_file_stream_create (&in, arg, MU_STREAM_READ));
MU_ASSERT (mu_stream_copy (str, in, 0, NULL));
mu_stream_destroy (&in);
}
}
mu_stream_destroy (&str);
return 0;
}
# This file is part of GNU Mailutils. -*- Autotest -*-
# Copyright (C) 2016 Free Software Foundation, Inc.
#
# GNU Mailutils is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3, or (at
# your option) any later version.
#
# GNU Mailutils is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
AT_SETUP([Fixed margins])
AT_KEYWORDS([wordwrap wordwrap00])
AT_DATA([input],[My Father had a small Estate in Nottinghamshire; I was the Third of five Sons. He sent me to Emanuel-College in Cambridge, at Fourteen Years old, where I resided three Years, and applyed my self close to my Studies: But the Charge of maintaining me (although I had a very scanty Allowance) being too great for a narrow Fortune; I was bound Apprentice to Mr. James Bates, an eminent Surgeon in London, with whom I continued four Years; and my Father now and then sending me small Sums of Money, I laid them out in learning Navigation, and other parts of the Mathematicks, useful to those who intend to travel, as I always believed it would be some time or other my Fortune to do. When I left Mr. Bates, I went down to my Father; where, by the Assistance of him and my Uncle John, and some other Relations, I got Forty Pounds, and a Promise of Thirty Pounds a Year to maintain me at Leyden: There I studied Physick two Years and seven Months, knowing it would be useful in long Voyages.
])
AT_CHECK([wordwrap -l 20 -r 70 input],
[0],
[ My Father had a small Estate in Nottinghamshire; I
was the Third of five Sons. He sent me to
Emanuel-College in Cambridge, at Fourteen Years
old, where I resided three Years, and applyed my
self close to my Studies: But the Charge of
maintaining me (although I had a very scanty
Allowance) being too great for a narrow Fortune; I
was bound Apprentice to Mr. James Bates, an
eminent Surgeon in London, with whom I continued
four Years; and my Father now and then sending me
small Sums of Money, I laid them out in learning
Navigation, and other parts of the Mathematicks,
useful to those who intend to travel, as I always
believed it would be some time or other my Fortune
to do. When I left Mr. Bates, I went down to my
Father; where, by the Assistance of him and my
Uncle John, and some other Relations, I got Forty
Pounds, and a Promise of Thirty Pounds a Year to
maintain me at Leyden: There I studied Physick two
Years and seven Months, knowing it would be useful
in long Voyages.
])
AT_CLEANUP
\ No newline at end of file
# This file is part of GNU Mailutils. -*- Autotest -*-
# Copyright (C) 2016 Free Software Foundation, Inc.
#
# GNU Mailutils is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3, or (at
# your option) any later version.
#
# GNU Mailutils is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
AT_SETUP([Move margin right])
AT_KEYWORDS([wordwrap wordwrap01])
AT_DATA([input0],[My Father had a small Estate in Nottinghamshire; I was the Third of five Sons.
])
AT_DATA([input1],[He sent me to Emanuel-College in Cambridge, at Fourteen Years old, where I resided three Years, and applyed my self close to my Studies:
])
AT_CHECK([cat input0 | tr -d '\n' | wordwrap -l 20 -r 70 - -l +8 input1],
[0],
[ My Father had a small Estate in Nottinghamshire; I
was the Third of five Sons. He sent me to
Emanuel-College
in Cambridge,
at Fourteen
Years old,
where I resided
three Years,
and applyed my
self close to
my Studies:
])
AT_CLEANUP
\ No newline at end of file
# This file is part of GNU Mailutils. -*- Autotest -*-
# Copyright (C) 2016 Free Software Foundation, Inc.
#
# GNU Mailutils is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3, or (at
# your option) any later version.
#
# GNU Mailutils is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
AT_SETUP([Move margin left])
AT_KEYWORDS([wordwrap wordwrap02])
AT_DATA([input0],[My Father had a small Estate in Nottinghamshire; I was the Third of five Sons.
])
AT_DATA([input1],[He sent me to Emanuel-College in Cambridge, at Fourteen Years old, where I resided three Years, and applyed my self close to my Studies:
])
AT_CHECK([cat input0 | tr -d '\n' | wordwrap -l 20 -r 70 - -l -8 input1],
[0],
[ My Father had a small Estate in Nottinghamshire; I
was the Third of five Sons.
He sent me to Emanuel-College
in Cambridge, at Fourteen Years
old, where I resided three
Years, and applyed my self
close to my Studies:
])
AT_CLEANUP
\ No newline at end of file
# This file is part of GNU Mailutils. -*- Autotest -*-
# Copyright (C) 2016 Free Software Foundation, Inc.
#
# GNU Mailutils is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3, or (at
# your option) any later version.
#
# GNU Mailutils is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
AT_SETUP([Corner cases])
AT_KEYWORDS([wordwrap wordwrap03])
AT_CHECK([echo abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz|\
wordwrap -r 10 -
],
[0],
[abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
])
AT_CLEANUP
\ No newline at end of file