libexplain  1.4.D001
libexplain/wrap_and_print.c
Go to the documentation of this file.
00001 /*
00002  * libexplain - Explain errno values returned by libc functions
00003  * Copyright (C) 2008-2011, 2013 Peter Miller
00004  * Written by Peter Miller <pmiller@opensource.org.au>
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU Lesser General Public License as published by
00008  * the Free Software Foundation; either version 3 of the License, or (at
00009  * your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  * Lesser General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU Lesser General Public License
00017  * along with this program. If not, see <http://www.gnu.org/licenses/>.
00018  */
00019 
00020 #include <libexplain/ac/assert.h>
00021 #include <libexplain/ac/ctype.h>
00022 #include <libexplain/ac/limits.h> /* for PATH_MAX on Solaris */
00023 #include <libexplain/ac/stdlib.h>
00024 #include <libexplain/ac/string.h>
00025 #include <libexplain/ac/sys/ioctl.h>
00026 #include <libexplain/ac/sys/param.h> /* for PATH_MAX except Solaris */
00027 #include <libexplain/ac/termios.h>
00028 #include <libexplain/ac/unistd.h>
00029 #include <libexplain/ac/wchar.h>
00030 #include <libexplain/ac/wctype.h>
00031 
00032 #ifdef HAVE_winsize_SYS_IOCTL_H
00033 #include <libexplain/ac/sys/ioctl.h>
00034 #endif
00035 
00036 #include <libexplain/option.h>
00037 #include <libexplain/string_buffer.h>
00038 #include <libexplain/wrap_and_print.h>
00039 
00040 #define DEFAULT_LINE_WIDTH 75
00041 
00042 #define MAX_LINE_LENGTH (PATH_MAX + 10)
00043 
00044 
00045 #if defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH)
00046 
00047 void
00048 explain_wrap_and_print_width(FILE *fp, const char *text, int width)
00049 {
00050     const char      *cp;
00051     const char      *end;
00052     char            line_string[MAX_LINE_LENGTH + 1];
00053     explain_string_buffer_t line_buf;
00054     char            word_string[MAX_LINE_LENGTH + 1];
00055     explain_string_buffer_t word_buf;
00056     static mbstate_t mbz;
00057     mbstate_t       state;
00058     int             width_of_line;
00059     int             width_of_word;
00060     int             hanging_indent;
00061     int             first_line;
00062 
00063     assert(width > 0);
00064     if (width <= 0)
00065         width = DEFAULT_LINE_WIDTH ;
00066     if (width > MAX_LINE_LENGTH)
00067         width = MAX_LINE_LENGTH;
00068     hanging_indent = explain_option_hanging_indent(width);
00069     assert(sizeof(word_string) <= sizeof(line_string));
00070     cp = text;
00071     end = text + strlen(text);
00072     explain_string_buffer_init(&line_buf, line_string, sizeof(line_string));
00073     explain_string_buffer_init(&word_buf, word_string, sizeof(word_string));
00074     state = mbz;
00075     width_of_line = 0;
00076     width_of_word = 0;
00077     first_line = 1;
00078     for (;;)
00079     {
00080         const char      *starts_here;
00081         wchar_t         wc;
00082         size_t          n;
00083 
00084         starts_here = cp;
00085         n = mbrtowc(&wc, cp, end - cp, &state);
00086         if ((ssize_t)n < 0)
00087         {
00088             wc = *cp;
00089             n = 1;
00090         }
00091         cp += n;
00092         if (n == 0 || wc == L'\0')
00093         {
00094             if (line_buf.position)
00095             {
00096                 if (0 == fwrite(line_string, line_buf.position, 1, fp))
00097                     return;
00098                 putc('\n', fp);
00099             }
00100             return;
00101         }
00102 
00103         if (iswspace(wc))
00104             continue;
00105 
00106         /*
00107          * Grab the next word.
00108          *
00109          * The width_of_word records the number of character positions
00110          * consumed.  This can be less than the number of bytes, when
00111          * multi-byte character sequences represent single displayed
00112          * characters.  This can be more than the number of bytes, for
00113          * exmaple kanji, when a character is display 2 columns wide.
00114          */
00115         word_buf.position = 0;
00116         width_of_word = 0;
00117         for (;;)
00118         {
00119             mbstate_t       hold;
00120 
00121             explain_string_buffer_write(&word_buf, starts_here, n);
00122             width_of_word += wcwidth(wc);
00123             if (explain_string_buffer_full(&word_buf))
00124                 break;
00125 
00126             hold = state;
00127             starts_here = cp;
00128             n = mbrtowc(&wc, cp, end - cp, &state);
00129             if ((ssize_t)n < 0)
00130             {
00131                 wc = *cp;
00132                 n = 1;
00133             }
00134 
00135             if (n == 0 || wc == '\0')
00136             {
00137                 state = hold;
00138                 break;
00139             }
00140             if (iswspace(wc))
00141             {
00142                 state = hold;
00143                 break;
00144             }
00145             cp += n;
00146         }
00147 
00148         if (line_buf.position == 0)
00149         {
00150             /* do nothing */
00151         }
00152         else if
00153         (
00154             width_of_line + 1 + width_of_word
00155         <=
00156             width - (first_line ? 0 : hanging_indent))
00157         {
00158             explain_string_buffer_putc(&line_buf, ' ');
00159             ++width_of_line;
00160         }
00161         else
00162         {
00163             if (0 == fwrite(line_string, line_buf.position, 1, fp))
00164                 return;
00165             putc('\n', fp);
00166             line_buf.position = 0;
00167             width_of_line = 0;
00168             first_line = 0;
00169         }
00170         if (line_buf.position == 0 && !first_line && hanging_indent)
00171         {
00172             for (;;)
00173             {
00174                 explain_string_buffer_putc(&line_buf, ' ');
00175                 if (line_buf.position >= (size_t)hanging_indent)
00176                     break;
00177             }
00178         }
00179         explain_string_buffer_puts(&line_buf, word_string);
00180         width_of_line += width_of_word;
00181 
00182         /*
00183          * Note: it is possible for a line to be longer than (width)
00184          * when it contains a single word that is itself longer than
00185          * (width).  We do this to avoid putting line breaks in the
00186          * middle of pathnames, provided the pathanme itself does not
00187          * contain white space.  This is useful for copy-and-paste.
00188          */
00189     }
00190 }
00191 
00192 #else
00193 
00194 void
00195 explain_wrap_and_print_width(FILE *fp, const char *text, int width)
00196 {
00197     const char      *cp;
00198     char            line_string[MAX_LINE_LENGTH + 1];
00199     explain_string_buffer_t line_buf;
00200     char            word_string[MAX_LINE_LENGTH + 1];
00201     explain_string_buffer_t word_buf;
00202     unsigned        hanging_indent;
00203     int             first_line;
00204 
00205     assert(width > 0);
00206     if (width <= 0)
00207         width = DEFAULT_LINE_WIDTH ;
00208     if (width > MAX_LINE_LENGTH)
00209         width = MAX_LINE_LENGTH;
00210     hanging_indent = explain_option_hanging_indent(width);
00211     assert(sizeof(word_string) <= sizeof(line_string));
00212     cp = text;
00213     explain_string_buffer_init(&line_buf, line_string, sizeof(line_string));
00214     explain_string_buffer_init(&word_buf, word_string, sizeof(word_string));
00215     first_line = 1;
00216     for (;;)
00217     {
00218         unsigned char c = *cp++;
00219         if (c == '\0')
00220         {
00221             if (line_buf.position)
00222             {
00223                 if (0 == fwrite(line_string, line_buf.position, 1, fp))
00224                     return;
00225                 putc('\n', fp);
00226             }
00227             return;
00228         }
00229 
00230         if (isspace(c))
00231             continue;
00232 
00233         /*
00234          * Grab the next word.
00235          */
00236         word_buf.position = 0;
00237         for (;;)
00238         {
00239             explain_string_buffer_putc(&word_buf, c);
00240             if (explain_string_buffer_full(&word_buf))
00241                 break;
00242             c = *cp;
00243             if (c == '\0')
00244                 break;
00245             if (isspace(c))
00246                 break;
00247             ++cp;
00248         }
00249 
00250         if (line_buf.position == 0)
00251         {
00252             /* do nothing */
00253         }
00254         else if
00255         (
00256             line_buf.position + 1 + word_buf.position
00257         <=
00258             (size_t)(width - (first_line ? 0 : hanging_indent))
00259         )
00260         {
00261             explain_string_buffer_putc(&line_buf, ' ');
00262         }
00263         else
00264         {
00265             if (0 == fwrite(line_string, line_buf.position, 1, fp))
00266                 return;
00267             putc('\n', fp);
00268             line_buf.position = 0;
00269             first_line = 0;
00270         }
00271         if (line_buf.position == 0 && !first_line && hanging_indent)
00272         {
00273             for (;;)
00274             {
00275                 explain_string_buffer_putc(&line_buf, ' ');
00276                 if (line_buf.position >= hanging_indent)
00277                     break;
00278             }
00279         }
00280         explain_string_buffer_puts(&line_buf, word_string);
00281         /*
00282          * Note: it is possible for a line to be longer than (width)
00283          * when it contains a single word that is itself longer than
00284          * (width).
00285          */
00286     }
00287 }
00288 
00289 #endif
00290 
00291 
00292 void
00293 explain_wrap_and_print(FILE *fp, const char *text)
00294 {
00295     int             width;
00296     int             fildes;
00297 
00298     width = DEFAULT_LINE_WIDTH;
00299 
00300     /*
00301      * If output is going to a terminal, use the terminal's with when
00302      * formatting error messages.
00303      */
00304     fildes = fileno(fp);
00305     if (isatty(fildes))
00306     {
00307         const char      *cp;
00308 
00309         cp = getenv("COLS");
00310         if (cp && *cp)
00311         {
00312             char            *ep;
00313 
00314             width = strtol(cp, &ep, 0);
00315             if (ep == cp || *ep)
00316                 width = 0;
00317         }
00318 #ifdef TIOCGWINSZ
00319         if (width <= 0 )
00320         {
00321             struct winsize  ws;
00322 
00323             if (ioctl(fildes, TIOCGWINSZ, &ws) >= 0)
00324                 width = ws.ws_col;
00325         }
00326 #endif
00327         if (width <= 0)
00328             width = DEFAULT_LINE_WIDTH;
00329     }
00330 
00331     /*
00332      * Print the text using the window width.
00333      */
00334     explain_wrap_and_print_width(fp, text, width);
00335 }
00336 
00337 
00338 /* vim: set ts=8 sw=4 et : */