Commit b4c656a1 b4c656a1d9eb814463b064ecff74848b901f55f4 by Sergey Poznyakoff

Implemented -t. Changed implementation of -Q.

Rewritten action() to correctly handle multibyte characters
and to provide for eventual using the BIDI algorithm.
(print_line,format_field,format_field_simple)
(format_field_align): New functions
(util_getcols): Measure /dev/tty if unable to open stdout.
(to work correctly with piped output).
(rfc2047_decode_wrapper): Cache the determined locale.
(get_personal): Do not limit the returned string length.
Do not attempt to decode the raw header text, since
parse822 will skip non-ascii characters, decode the already
obtained personal part instead.
1 parent 00770353
Showing 1 changed file with 258 additions and 126 deletions
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
24 #include <stdlib.h> 24 #include <stdlib.h>
25 #include <string.h> 25 #include <string.h>
26 #include <unistd.h> 26 #include <unistd.h>
27 #include <fcntl.h>
27 #include <sys/types.h> 28 #include <sys/types.h>
28 #ifdef HAVE_TERMIOS_H 29 #ifdef HAVE_TERMIOS_H
29 # include <termios.h> 30 # include <termios.h>
...@@ -31,6 +32,16 @@ ...@@ -31,6 +32,16 @@
31 #include <sys/ioctl.h> 32 #include <sys/ioctl.h>
32 #include <sys/stat.h> 33 #include <sys/stat.h>
33 34
35 #ifdef HAVE_ICONV_H
36 # include <iconv.h>
37 #endif
38 #ifndef MB_LEN_MAX
39 # define MB_LEN_MAX 4
40 #endif
41
42 #include <mbswidth.h>
43 #include <xalloc.h>
44
34 #include <mailutils/address.h> 45 #include <mailutils/address.h>
35 #include <mailutils/argp.h> 46 #include <mailutils/argp.h>
36 #include <mailutils/attribute.h> 47 #include <mailutils/attribute.h>
...@@ -50,16 +61,17 @@ ...@@ -50,16 +61,17 @@
50 #include <mailutils/mutil.h> 61 #include <mailutils/mutil.h>
51 #include <mailutils/mime.h> 62 #include <mailutils/mime.h>
52 63
53 static char *show_field; 64 static char *show_field; /* Show this header field instead of the default
54 static int show_to; 65 `From: Subject:' pair. -f option */
55 static int show_from = 1; 66 static int show_to; /* Additionally display To: field. -l option */
56 static int show_subject = 1; 67 static int show_number; /* Prefix each line with the message number. -n */
57 static int show_number; 68 static int show_summary; /* Summarize the number of messages by message
58 static int show_summary; 69 status in each mailbox. -S option */
59 static int be_quiet; 70 static int be_quiet; /* Quiet mode. -q option. */
60 static int align = 1; 71 static int show_query; /* Additional flag toggled by -q to display
61 static int show_query; 72 a one-line summary for each mailbox */
62 static int dbug; 73 static int align = 0; /* Tidy mode. -t option. */
74 static int dbug; /* Debug level. -d option.*/
63 75
64 #define IS_READ 0x001 76 #define IS_READ 0x001
65 #define IS_OLD 0x010 77 #define IS_OLD 0x010
...@@ -179,10 +191,139 @@ static struct argp_option options[] = { ...@@ -179,10 +191,139 @@ static struct argp_option options[] = {
179 {"query", 'q', NULL, 0, N_("Print a message if the mailbox contains some unread mail"), 0}, 191 {"query", 'q', NULL, 0, N_("Print a message if the mailbox contains some unread mail"), 0},
180 {"summary",'S', NULL, 0, N_("Print a summary of messages"), 0}, 192 {"summary",'S', NULL, 0, N_("Print a summary of messages"), 0},
181 {"status", 's', N_("STATUS"), 0, attr_help, 0}, 193 {"status", 's', N_("STATUS"), 0, attr_help, 0},
182 {"align", 't', NULL, 0, N_("Try to align"), 0}, 194 {"align", 't', NULL, 0, N_("Tidy mode: align subject lines"), 0},
183 {0, 0, 0, 0} 195 {0, 0, 0, 0}
184 }; 196 };
185 197
198 /* Number of columns in output:
199
200 Maximum 4 message number, to, from, subject -ln
201 Default 2 from, subject [none]
202 Minimum 1 FIELD -f FIELD
203 */
204
205 static int numfields; /* Number of output fields */
206 static int fieldwidth[4]; /* Field start positions */
207 static char *linebuf; /* Output line buffer */
208 static size_t linemax; /* Size of linebuf */
209 static size_t linepos; /* Position in the output line buffer */
210 static int curfield; /* Current output field */
211 static int nextstart; /* Start position of the next field */
212 static int curcol; /* Current output column */
213
214 typedef void (*fmt_formatter) (const char *fmt, ...);
215
216 static fmt_formatter format_field;
217
218 void
219 print_line ()
220 {
221 if (linebuf)
222 {
223 puts (linebuf);
224 linebuf[0] = 0;
225 linepos = 0;
226 curcol = nextstart = 0;
227 curfield = 0;
228 }
229 else
230 putchar ('\n');
231 }
232
233 void
234 format_field_simple (const char *fmt, ...)
235 {
236 va_list ap;
237 va_start (ap, fmt);
238 vprintf (fmt, ap);
239 putchar (' ');
240 va_end (ap);
241 }
242
243 void
244 format_field_align (const char *fmt, ...)
245 {
246 size_t n, width;
247 va_list ap;
248
249 va_start (ap, fmt);
250 if (nextstart != 0)
251 {
252 if (curcol >= nextstart)
253 {
254 if (curfield == numfields - 1)
255 {
256 puts (linebuf);
257 linepos = 0;
258 printf ("%*s", nextstart, "");
259 }
260 else
261 {
262 linebuf[linepos++] = ' ';
263 curcol++;
264 }
265 }
266 else if (nextstart != curcol)
267 {
268 /* align to field start */
269 n = snprintf (linebuf + linepos, linemax - linepos,
270 "%*s", nextstart - curcol, "");
271 linepos += n;
272 curcol = nextstart;
273 }
274 }
275
276 n = vsnprintf (linebuf + linepos, linemax - linepos, fmt, ap);
277 va_end (ap);
278
279 /* Compute output width */
280 if (curfield == numfields - 1)
281 {
282 for ( ; n > 0; n--)
283 {
284 int c = linebuf[linepos + n];
285 linebuf[linepos + n] = 0;
286 width = mbswidth (linebuf + linepos, 0);
287 if (width <= fieldwidth[curfield])
288 break;
289 linebuf[linepos + n] = c;
290 }
291 }
292 else
293 width = mbswidth (linebuf + linepos, 0);
294
295 /* Increment counters */
296 linepos += n;
297 curcol += width;
298 nextstart += fieldwidth[curfield++];
299 }
300
301 /*
302 * Get the number of columns on the screen
303 * First try an ioctl() call not all shells set the COLUMNS environ.
304 * This function was taken from mail/util.c.
305 */
306 int
307 util_getcols (void)
308 {
309 struct winsize ws;
310
311 ws.ws_col = ws.ws_row = 0;
312 if (ioctl (1, TIOCGWINSZ, (char *) &ws) < 0)
313 {
314 int fd = open ("/dev/tty", O_RDWR);
315 ioctl (fd, TIOCGWINSZ, (char *) &ws);
316 close (fd);
317 }
318 if (ws.ws_row == 0)
319 {
320 const char *columns = getenv ("COLUMNS");
321 if (columns)
322 ws.ws_col = strtol (columns, NULL, 10);
323 }
324 return ws.ws_col;
325 }
326
186 static error_t 327 static error_t
187 parse_opt (int key, char *arg, struct argp_state *state) 328 parse_opt (int key, char *arg, struct argp_state *state)
188 { 329 {
...@@ -194,8 +335,6 @@ parse_opt (int key, char *arg, struct argp_state *state) ...@@ -194,8 +335,6 @@ parse_opt (int key, char *arg, struct argp_state *state)
194 335
195 case 'f': 336 case 'f':
196 show_field = arg; 337 show_field = arg;
197 show_from = 0;
198 show_subject = 0;
199 align = 0; 338 align = 0;
200 break; 339 break;
201 340
...@@ -210,11 +349,6 @@ parse_opt (int key, char *arg, struct argp_state *state) ...@@ -210,11 +349,6 @@ parse_opt (int key, char *arg, struct argp_state *state)
210 case 'Q': 349 case 'Q':
211 /* Very silent. */ 350 /* Very silent. */
212 be_quiet += 2; 351 be_quiet += 2;
213 if (freopen ("/dev/null", "w", stdout) == NULL)
214 {
215 perror (_("Cannot be very quiet"));
216 exit (3);
217 }
218 break; 352 break;
219 353
220 case 'q': 354 case 'q':
...@@ -233,6 +367,42 @@ parse_opt (int key, char *arg, struct argp_state *state) ...@@ -233,6 +367,42 @@ parse_opt (int key, char *arg, struct argp_state *state)
233 case 't': 367 case 't':
234 align = 1; 368 align = 1;
235 break; 369 break;
370
371 case ARGP_KEY_FINI:
372 if (align && (linemax = util_getcols ()))
373 {
374 int i;
375 size_t width = 0;
376
377 format_field = format_field_align;
378
379 /* Allocate the line buffer */
380 linemax = linemax * MB_LEN_MAX + 1;
381 linebuf = xmalloc (linemax);
382
383 /* Set up column widths */
384 if (show_number)
385 fieldwidth[numfields++] = 5;
386
387 if (show_to)
388 fieldwidth[numfields++] = 20;
389
390 if (show_field)
391 fieldwidth[numfields++] = 0;
392 else
393 {
394 fieldwidth[numfields++] = 20;
395 fieldwidth[numfields++] = 0;
396 }
397
398 for (i = 0; i < numfields; i++)
399 width += fieldwidth[i];
400
401 fieldwidth[numfields-1] = util_getcols () - width;
402 }
403 else
404 format_field = format_field_simple;
405 break;
236 406
237 default: 407 default:
238 return ARGP_ERR_UNKNOWN; 408 return ARGP_ERR_UNKNOWN;
...@@ -267,36 +437,43 @@ static const char *frm_argp_capa[] = { ...@@ -267,36 +437,43 @@ static const char *frm_argp_capa[] = {
267 static char * 437 static char *
268 rfc2047_decode_wrapper (char *buf, size_t buflen) 438 rfc2047_decode_wrapper (char *buf, size_t buflen)
269 { 439 {
270 char locale[32];
271 char *charset = NULL;
272 char *tmp;
273 int rc; 440 int rc;
441 char *tmp;
442 static char *charset = NULL;
274 443
275 memset (locale, 0, sizeof (locale)); 444 if (!charset)
445 {
446 char locale[32];
276 447
277 /* Try to deduce the charset from LC_ALL or LANG variables */ 448 memset (locale, 0, sizeof (locale));
278 449
279 tmp = getenv ("LC_ALL"); 450 /* Try to deduce the charset from LC_ALL or LANG variables */
280 if (!tmp)
281 tmp = getenv ("LANG");
282 451
283 if (tmp) 452 tmp = getenv ("LC_ALL");
284 { 453 if (!tmp)
285 char *sp = NULL; 454 tmp = getenv ("LANG");
286 char *lang;
287 char *terr;
288 455
289 strncpy (locale, tmp, sizeof (locale) - 1); 456 if (tmp)
457 {
458 char *sp = NULL;
459 char *lang;
460 char *terr;
290 461
291 lang = strtok_r (locale, "_", &sp); 462 strncpy (locale, tmp, sizeof (locale) - 1);
292 terr = strtok_r (NULL, ".", &sp); 463
293 charset = strtok_r (NULL, "@", &sp); 464 lang = strtok_r (locale, "_", &sp);
465 terr = strtok_r (NULL, ".", &sp);
466 charset = strtok_r (NULL, "@", &sp);
467
468 if (!charset)
469 charset = mu_charset_lookup (lang, terr);
294 470
295 if (!charset) 471 if (!charset)
296 charset = mu_charset_lookup (lang, terr); 472 charset = "ASCII";
473 }
297 } 474 }
298 475
299 if (!charset) 476 if (strcmp (charset, "ASCII") == 0)
300 return strdup (buf); 477 return strdup (buf);
301 478
302 rc = rfc2047_decode (charset, buf, &tmp); 479 rc = rfc2047_decode (charset, buf, &tmp);
...@@ -313,69 +490,46 @@ rfc2047_decode_wrapper (char *buf, size_t buflen) ...@@ -313,69 +490,46 @@ rfc2047_decode_wrapper (char *buf, size_t buflen)
313 490
314 /* Retrieve the Personal Name from the header To: or From: */ 491 /* Retrieve the Personal Name from the header To: or From: */
315 static int 492 static int
316 get_personal (header_t hdr, const char *field, char *personal, size_t buflen) 493 get_personal (header_t hdr, const char *field, char **personal)
317 { 494 {
318 char hfield[512]; 495 char *hfield;
319 int status; 496 int status;
320 497
321 /* Empty string. */ 498 status = header_aget_value_unfold (hdr, field, &hfield);
322 *hfield = '\0';
323
324 status = header_get_value_unfold (hdr, field, hfield, sizeof (hfield), NULL);
325 if (status == 0) 499 if (status == 0)
326 { 500 {
327 address_t address = NULL; 501 address_t address = NULL;
328 size_t len = 0; 502 char *s;
329 503
330 char *s = rfc2047_decode_wrapper (hfield, strlen (hfield)); 504 address_create (&address, hfield);
331 address_create (&address, s);
332 free (s);
333 505
334 address_get_personal (address, 1, personal, buflen, &len); 506 address_aget_personal (address, 1, &s);
335 address_destroy (&address); 507 address_destroy (&address);
508 if (s == NULL)
509 s = hfield;
510 else
511 free (hfield);
336 512
337 if (len == 0) 513 *personal = rfc2047_decode_wrapper (s, strlen (s));
338 strncpy (personal, hfield, buflen)[buflen - 1] = '\0'; 514 free (s);
339 } 515 }
340 return status; 516 return status;
341 } 517 }
342 518
343 static struct { 519 static struct
520 {
344 size_t index; 521 size_t index;
345 size_t new; 522 size_t new;
346 size_t read; 523 size_t read;
347 size_t unread; 524 size_t unread;
348 } counter; 525 } counter;
349 526
350 /*
351 * Get the number of columns on the screen
352 * First try an ioctl() call not all shells set the COLUMNS environ.
353 * This function was taken from mail/util.c.
354 */
355 int
356 util_getcols (void)
357 {
358 struct winsize ws;
359
360 ws.ws_col = ws.ws_row = 0;
361 if ((ioctl(1, TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_row == 0)
362 {
363 const char *columns = getenv ("COLUMNS");
364 if (columns)
365 ws.ws_col = strtol (columns, NULL, 10);
366 }
367
368 /* FIXME: Should we exit()/abort() if col <= 0 ? */
369 return ws.ws_col;
370 }
371
372 /* Observable action is being called on discovery of each message. */ 527 /* Observable action is being called on discovery of each message. */
373 /* FIXME: The format of the display is poorly done, please correct. */ 528 /* FIXME: The format of the display is poorly done, please correct. */
374 static int 529 static int
375 action (observer_t o, size_t type) 530 action (observer_t o, size_t type)
376 { 531 {
377 int status; 532 int status;
378 int col_cnt = 0;
379 533
380 switch (type) 534 switch (type)
381 { 535 {
...@@ -412,79 +566,57 @@ action (observer_t o, size_t type) ...@@ -412,79 +566,57 @@ action (observer_t o, size_t type)
412 break; 566 break;
413 567
414 if (show_number) 568 if (show_number)
415 { 569 format_field ("%4lu: ", (u_long) counter.index);
416 printf ("%4lu: ", (u_long) counter.index);
417 col_cnt += 6;
418 }
419
420 if (show_field) /* FIXME: This should be also rfc2047_decode. */
421 {
422 char hfield[256];
423 status = header_get_value_unfold (hdr, show_field, hfield,
424 sizeof (hfield), NULL);
425 if (status == 0)
426 printf ("%s", hfield);
427 }
428 570
429 if (show_to) 571 if (show_to)
430 { 572 {
431 char hto[16]; 573 char *hto;
432 status = get_personal (hdr, MU_HEADER_TO, hto, sizeof (hto)); 574 status = get_personal (hdr, MU_HEADER_TO, &hto);
433 575
434 if (status == 0) 576 if (status == 0)
435 { 577 {
436 printf ("(%s) ", hto); 578 format_field ("(%s) ", hto);
437 col_cnt += strlen (hto) + 3; 579 free (hto);
438 } 580 }
439 else 581 else
440 { 582 format_field ("(none)");
441 printf ("(-----) ");
442 col_cnt += 8;
443 }
444 } 583 }
445 584
446 if (show_from) 585 if (show_field) /* FIXME: This should be also rfc2047_decode. */
447 { 586 {
448 char hfrom[32]; 587 char *hfield;
449 status = get_personal (hdr, MU_HEADER_FROM, hfrom, sizeof (hfrom)); 588 status = header_aget_value_unfold (hdr, show_field, &hfield);
450 if (status == 0) 589 if (status == 0)
451 { 590 {
452 printf ("%s\t", hfrom); 591 format_field ("%s", hfield);
453 col_cnt += strlen (hfrom) + 4; // tab=4, sigh. 592 free (hfield);
454 } 593 }
455 else 594 else
595 format_field ("");
596 }
597 else
598 {
599 char *tmp;
600 status = get_personal (hdr, MU_HEADER_FROM, &tmp);
601 if (status == 0)
456 { 602 {
457 printf ("-----\t"); 603 format_field ("%s", tmp);
458 col_cnt += 9; 604 free (tmp);
459 } 605 }
460 } 606 else
461 607 format_field ("");
462 /*
463 A temporary fix for (correct) displaying:
464 util_getcols() - col_cnt - ~1. It's ugly, I know.
465 */
466 608
467 if (show_subject)
468 {
469 char *hsubject;
470 status = header_aget_value_unfold (hdr, MU_HEADER_SUBJECT, 609 status = header_aget_value_unfold (hdr, MU_HEADER_SUBJECT,
471 &hsubject); 610 &tmp);
472 if (status == 0) 611 if (status == 0)
473 { 612 {
474 char *s = rfc2047_decode_wrapper (hsubject, strlen (hsubject)); 613 char *s = rfc2047_decode_wrapper (tmp, strlen (tmp));
475 char out[80]; 614 format_field ("%s", s);
476 int fspace = util_getcols () - col_cnt - 1;
477
478 fspace = (fspace > (sizeof (out) - 1))
479 ? (sizeof (out) - 1) : fspace;
480
481 memset (out, 0, sizeof (out));
482 strncpy (out, s, fspace);
483 printf ("%s", out);
484 free (s); 615 free (s);
616 free (tmp);
485 } 617 }
486 } 618 }
487 putchar ('\n'); 619 print_line ();
488 break; 620 break;
489 } 621 }
490 622
......