Add psql '\pset format wrapped' mode to wrap output to screen width, or

file/pipe output too if \pset columns' is set.

Bryce Nesbitt
This commit is contained in:
Bruce Momjian 2008-05-08 17:04:26 +00:00
parent eb915caf92
commit 5adf98ae24
6 changed files with 447 additions and 200 deletions

View File

@ -1,5 +1,5 @@
<!-- <!--
$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.202 2008/05/08 00:27:57 momjian Exp $ $PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.203 2008/05/08 17:04:26 momjian Exp $
PostgreSQL documentation PostgreSQL documentation
--> -->
@ -1514,7 +1514,8 @@ lo_import 152801
<listitem> <listitem>
<para> <para>
Sets the output format to one of <literal>unaligned</literal>, Sets the output format to one of <literal>unaligned</literal>,
<literal>aligned</literal>, <literal>html</literal>, <literal>aligned</literal>, <literal>wrapped</literal>,
<literal>html</literal>,
<literal>latex</literal>, or <literal>troff-ms</literal>. <literal>latex</literal>, or <literal>troff-ms</literal>.
Unique abbreviations are allowed. (That would mean one letter Unique abbreviations are allowed. (That would mean one letter
is enough.) is enough.)
@ -1526,8 +1527,21 @@ lo_import 152801
is intended to create output that might be intended to be read is intended to create output that might be intended to be read
in by other programs (tab-separated, comma-separated). in by other programs (tab-separated, comma-separated).
<quote>Aligned</quote> mode is the standard, human-readable, <quote>Aligned</quote> mode is the standard, human-readable,
nicely formatted text output that is default. The nicely formatted text output that is default.
<quote><acronym>HTML</acronym></quote> and </para>
<para>
<quote>Wrapped</quote> is like <literal>aligned</> but wraps
output to the specified width. If <literal>\pset columns</> is
zero (the default), <literal>wrapped</> mode only affects screen
output and wrapped width is controlled by the environment
variable <envar>COLUMNS</> or the detected screen width. If
<literal>\pset columns</> is set to a non-zero value, all output
is wrapped, including file and pipe output.
</para>
<para>
The <quote><acronym>HTML</acronym></quote> and
<quote>LaTeX</quote> modes put out tables that are intended to <quote>LaTeX</quote> modes put out tables that are intended to
be included in documents using the respective mark-up be included in documents using the respective mark-up
language. They are not complete documents! (This might not be language. They are not complete documents! (This might not be
@ -1537,6 +1551,17 @@ lo_import 152801
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><literal>columns</literal></term>
<listitem>
<para>
Controls the target width for the <literal>wrapped</> format.
Zero (the default) causes the <literal>wrapped</> format to
affect only screen output.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><literal>border</literal></term> <term><literal>border</literal></term>
<listitem> <listitem>
@ -2706,6 +2731,18 @@ $endif
<title>Environment</title> <title>Environment</title>
<variablelist> <variablelist>
<varlistentry>
<term><envar>COLUMNS</envar></term>
<listitem>
<para>
Used for the <literal>wrapped</> output format if
<literal>\pset columns</> is zero.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><envar>PAGER</envar></term> <term><envar>PAGER</envar></term>

View File

@ -3,7 +3,7 @@
* *
* Copyright (c) 2000-2008, PostgreSQL Global Development Group * Copyright (c) 2000-2008, PostgreSQL Global Development Group
* *
* $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.187 2008/05/02 09:27:50 petere Exp $ * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.188 2008/05/08 17:04:26 momjian Exp $
*/ */
#include "postgres_fe.h" #include "postgres_fe.h"
#include "command.h" #include "command.h"
@ -1502,6 +1502,9 @@ _align2string(enum printFormat in)
case PRINT_ALIGNED: case PRINT_ALIGNED:
return "aligned"; return "aligned";
break; break;
case PRINT_WRAPPED:
return "wrapped";
break;
case PRINT_HTML: case PRINT_HTML:
return "html"; return "html";
break; break;
@ -1535,6 +1538,8 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
popt->topt.format = PRINT_UNALIGNED; popt->topt.format = PRINT_UNALIGNED;
else if (pg_strncasecmp("aligned", value, vallen) == 0) else if (pg_strncasecmp("aligned", value, vallen) == 0)
popt->topt.format = PRINT_ALIGNED; popt->topt.format = PRINT_ALIGNED;
else if (pg_strncasecmp("wrapped", value, vallen) == 0)
popt->topt.format = PRINT_WRAPPED;
else if (pg_strncasecmp("html", value, vallen) == 0) else if (pg_strncasecmp("html", value, vallen) == 0)
popt->topt.format = PRINT_HTML; popt->topt.format = PRINT_HTML;
else if (pg_strncasecmp("latex", value, vallen) == 0) else if (pg_strncasecmp("latex", value, vallen) == 0)
@ -1543,7 +1548,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
popt->topt.format = PRINT_TROFF_MS; popt->topt.format = PRINT_TROFF_MS;
else else
{ {
psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n"); psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
return false; return false;
} }
@ -1724,6 +1729,16 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
} }
} }
/* set border style/width */
else if (strcmp(param, "columns") == 0)
{
if (value)
popt->topt.columns = atoi(value);
if (!quiet)
printf(_("Target width for \"wrapped\" format is %d.\n"), popt->topt.columns);
}
else else
{ {
psql_error("\\pset: unknown option: %s\n", param); psql_error("\\pset: unknown option: %s\n", param);

View File

@ -3,7 +3,7 @@
* *
* Copyright (c) 2000-2008, PostgreSQL Global Development Group * Copyright (c) 2000-2008, PostgreSQL Global Development Group
* *
* $PostgreSQL: pgsql/src/bin/psql/mbprint.c,v 1.30 2008/04/16 18:18:00 momjian Exp $ * $PostgreSQL: pgsql/src/bin/psql/mbprint.c,v 1.31 2008/05/08 17:04:26 momjian Exp $
* *
* XXX this file does not really belong in psql/. Perhaps move to libpq? * XXX this file does not really belong in psql/. Perhaps move to libpq?
* It also seems that the mbvalidate function is redundant with existing * It also seems that the mbvalidate function is redundant with existing
@ -204,8 +204,8 @@ pg_wcswidth(const unsigned char *pwcs, size_t len, int encoding)
/* /*
* pg_wcssize takes the given string in the given encoding and returns three * pg_wcssize takes the given string in the given encoding and returns three
* values: * values:
* result_width: Width in display character of longest line in string * result_width: Width in display characters of the longest line in string
* result_height: Number of lines in display output * result_height: Number of newlines in display output
* result_format_size: Number of bytes required to store formatted representation of string * result_format_size: Number of bytes required to store formatted representation of string
*/ */
int int
@ -279,6 +279,11 @@ pg_wcssize(unsigned char *pwcs, size_t len, int encoding, int *result_width,
return width; return width;
} }
/*
* Filter out unprintable characters, companion to wcs_size.
* Break input into lines based on \n. lineptr[i].ptr == NULL
* indicates the end of the array.
*/
void void
pg_wcsformat(unsigned char *pwcs, size_t len, int encoding, pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
struct lineptr *lines, int count) struct lineptr *lines, int count)
@ -307,6 +312,7 @@ pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
if (count == 0) if (count == 0)
exit(1); /* Screwup */ exit(1); /* Screwup */
/* make next line point to remaining memory */
lines->ptr = ptr; lines->ptr = ptr;
} }
else if (*pwcs == '\r') /* Linefeed */ else if (*pwcs == '\r') /* Linefeed */
@ -353,12 +359,13 @@ pg_wcsformat(unsigned char *pwcs, size_t len, int encoding,
} }
len -= chlen; len -= chlen;
} }
*ptr++ = '\0';
lines->width = linewidth; lines->width = linewidth;
lines++; *ptr++ = '\0'; /* Terminate formatted string */
count--;
if (count > 0) if (count == 0)
lines->ptr = NULL; exit(1); /* Screwup */
(lines+1)->ptr = NULL; /* terminate line array */
} }
unsigned char * unsigned char *

View File

@ -3,7 +3,7 @@
* *
* Copyright (c) 2000-2008, PostgreSQL Global Development Group * Copyright (c) 2000-2008, PostgreSQL Global Development Group
* *
* $PostgreSQL: pgsql/src/bin/psql/print.c,v 1.97 2008/03/27 03:57:34 tgl Exp $ * $PostgreSQL: pgsql/src/bin/psql/print.c,v 1.98 2008/05/08 17:04:26 momjian Exp $
*/ */
#include "postgres_fe.h" #include "postgres_fe.h"
@ -28,6 +28,8 @@
#include "mbprint.h" #include "mbprint.h"
static int strlen_max_width(unsigned char *str, int *target_width, int encoding);
/* /*
* We define the cancel_pressed flag in this file, rather than common.c where * We define the cancel_pressed flag in this file, rather than common.c where
* it naturally belongs, because this file is also used by non-psql programs * it naturally belongs, because this file is also used by non-psql programs
@ -43,6 +45,7 @@ static char *decimal_point;
static char *grouping; static char *grouping;
static char *thousands_sep; static char *thousands_sep;
static void * static void *
pg_local_malloc(size_t size) pg_local_malloc(size_t size)
{ {
@ -396,34 +399,43 @@ _print_horizontal_line(const unsigned int col_count, const unsigned int *widths,
} }
/*
* Prety pretty boxes around cells.
*/
static void static void
print_aligned_text(const char *title, const char *const * headers, print_aligned_text(const char *title, const char *const * headers,
const char *const * cells, const char *const * footers, const char *const * cells, const char *const * footers,
const char *opt_align, const printTableOpt *opt, const char *opt_align, const printTableOpt *opt,
FILE *fout) bool is_pager, FILE *fout)
{ {
bool opt_tuples_only = opt->tuples_only; bool opt_tuples_only = opt->tuples_only;
bool opt_numeric_locale = opt->numericLocale; bool opt_numeric_locale = opt->numericLocale;
unsigned short int opt_border = opt->border;
int encoding = opt->encoding; int encoding = opt->encoding;
unsigned int col_count = 0; unsigned short int opt_border = opt->border;
unsigned int cell_count = 0;
unsigned int i; unsigned int col_count = 0, cell_count = 0;
int tmp;
unsigned int *widths, unsigned int i,
total_w; j;
unsigned int *heights;
unsigned int *format_space; unsigned int *width_header,
*max_width,
*width_wrap,
*width_average;
unsigned int *max_nl_lines, /* value split by newlines */
*curr_nl_line,
*max_bytes;
unsigned char **format_buf; unsigned char **format_buf;
unsigned int width_total;
unsigned int total_header_width;
const char * const *ptr; const char * const *ptr;
struct lineptr **col_lineptrs; /* pointers to line pointer for each struct lineptr **col_lineptrs; /* pointers to line pointer per column */
* column */
struct lineptr *lineptr_list; /* complete list of linepointers */
int *complete; /* Array remembering which columns have bool *header_done; /* Have all header lines been output? */
* completed output */ int *bytes_output; /* Bytes output for column value */
int output_columns = 0; /* Width of interactive console */
if (cancel_pressed) if (cancel_pressed)
return; return;
@ -437,275 +449,404 @@ print_aligned_text(const char *title, const char *const * headers,
if (col_count > 0) if (col_count > 0)
{ {
widths = pg_local_calloc(col_count, sizeof(*widths)); width_header = pg_local_calloc(col_count, sizeof(*width_header));
heights = pg_local_calloc(col_count, sizeof(*heights)); width_average = pg_local_calloc(col_count, sizeof(*width_average));
max_width = pg_local_calloc(col_count, sizeof(*max_width));
width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap));
max_nl_lines = pg_local_calloc(col_count, sizeof(*max_nl_lines));
curr_nl_line = pg_local_calloc(col_count, sizeof(*curr_nl_line));
col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs));
format_space = pg_local_calloc(col_count, sizeof(*format_space)); max_bytes = pg_local_calloc(col_count, sizeof(*max_bytes));
format_buf = pg_local_calloc(col_count, sizeof(*format_buf)); format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
complete = pg_local_calloc(col_count, sizeof(*complete)); header_done = pg_local_calloc(col_count, sizeof(*header_done));
bytes_output = pg_local_calloc(col_count, sizeof(*bytes_output));
} }
else else
{ {
widths = NULL; width_header = NULL;
heights = NULL; width_average = NULL;
max_width = NULL;
width_wrap = NULL;
max_nl_lines = NULL;
curr_nl_line = NULL;
col_lineptrs = NULL; col_lineptrs = NULL;
format_space = NULL; max_bytes = NULL;
format_buf = NULL; format_buf = NULL;
complete = NULL; header_done = NULL;
bytes_output = NULL;
} }
/* count cells (rows * cols) */ /* scan all column headers, find maximum width and max max_nl_lines */
for (ptr = cells; *ptr; ptr++)
cell_count++;
/* calc column widths */
for (i = 0; i < col_count; i++) for (i = 0; i < col_count; i++)
{ {
/* Get width & height */ int width,
int height, nl_lines,
space; bytes_required;
pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &space); pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &nl_lines, &bytes_required);
if (tmp > widths[i]) if (width > max_width[i])
widths[i] = tmp; max_width[i] = width;
if (height > heights[i]) if (nl_lines > max_nl_lines[i])
heights[i] = height; max_nl_lines[i] = nl_lines;
if (space > format_space[i]) if (bytes_required > max_bytes[i])
format_space[i] = space; max_bytes[i] = bytes_required;
width_header[i] = width;
} }
for (i = 0, ptr = cells; *ptr; ptr++, i++) /* scan all cells, find maximum width, compute cell_count */
for (i = 0, ptr = cells; *ptr; ptr++, i++, cell_count++)
{ {
int numeric_locale_len; int width,
int height, nl_lines,
space; bytes_required;
if (opt_align[i % col_count] == 'r' && opt_numeric_locale) /* Get width, ignore nl_lines */
numeric_locale_len = additional_numeric_locale_len(*ptr); pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &nl_lines, &bytes_required);
else if (opt_numeric_locale && opt_align[i % col_count] == 'r')
numeric_locale_len = 0; {
width += additional_numeric_locale_len(*ptr);
/* Get width, ignore height */ bytes_required += additional_numeric_locale_len(*ptr);
pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &space);
tmp += numeric_locale_len;
if (tmp > widths[i % col_count])
widths[i % col_count] = tmp;
if (height > heights[i % col_count])
heights[i % col_count] = height;
if (space > format_space[i % col_count])
format_space[i % col_count] = space;
} }
if (width > max_width[i % col_count])
max_width[i % col_count] = width;
if (nl_lines > max_nl_lines[i % col_count])
max_nl_lines[i % col_count] = nl_lines;
if (bytes_required > max_bytes[i % col_count])
max_bytes[i % col_count] = bytes_required;
width_average[i % col_count] += width;
}
/* If we have rows, compute average */
if (col_count != 0 && cell_count != 0)
{
int rows = cell_count / col_count;
for (i = 0; i < col_count; i++)
width_average[i % col_count] /= rows;
}
/* adjust the total display width based on border style */
if (opt_border == 0) if (opt_border == 0)
total_w = col_count - 1; width_total = col_count - 1;
else if (opt_border == 1) else if (opt_border == 1)
total_w = col_count * 3 - 1; width_total = col_count * 3 - 1;
else else
total_w = col_count * 3 + 1; width_total = col_count * 3 + 1;
total_header_width = width_total;
for (i = 0; i < col_count; i++) for (i = 0; i < col_count; i++)
total_w += widths[i]; {
width_total += max_width[i];
total_header_width += width_header[i];
}
/* /*
* At this point: widths contains the max width of each column heights * At this point: max_width[] contains the max width of each column,
* contains the max height of a cell of each column format_space contains * max_nl_lines[] contains the max number of lines in each column,
* maximum space required to store formatted string so we prepare the * max_bytes[] contains the maximum storage space for formatting
* formatting structures * strings, width_total contains the giant width sum. Now we allocate
* some memory for line pointers.
*/ */
if (col_count > 0)
{
int heights_total = 0;
struct lineptr *lineptr;
for (i = 0; i < col_count; i++)
heights_total += heights[i];
lineptr = lineptr_list = pg_local_calloc(heights_total, sizeof(*lineptr_list));
for (i = 0; i < col_count; i++) for (i = 0; i < col_count; i++)
{ {
col_lineptrs[i] = lineptr; /* Add entry for ptr == NULL array termination */
lineptr += heights[i]; col_lineptrs[i] = pg_local_calloc(max_nl_lines[i] + 1,
sizeof(**col_lineptrs));
format_buf[i] = pg_local_malloc(format_space[i]); format_buf[i] = pg_local_malloc(max_bytes[i] + 1);
col_lineptrs[i]->ptr = format_buf[i]; col_lineptrs[i]->ptr = format_buf[i];
} }
}
else
lineptr_list = NULL;
/* Default word wrap to the full width, i.e. no word wrap */
for (i = 0; i < col_count; i++)
width_wrap[i] = max_width[i];
if (opt->format == PRINT_WRAPPED)
{
/* Get terminal width */
if (opt->columns > 0)
output_columns = opt->columns;
else if ((fout == stdout && isatty(fileno(stdout))) || is_pager)
{
if (opt->env_columns)
output_columns = opt->env_columns;
#ifdef TIOCGWINSZ
else
{
struct winsize screen_size;
if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
output_columns = screen_size.ws_col;
}
#endif
}
/*
* Optional optimized word wrap. Shrink columns with a high max/avg ratio.
* Slighly bias against wider columns. (Increases chance a narrow column
* will fit in its cell.)
* If available columns is positive...
* and greater than the width of the unshrinkable column headers
*/
if (output_columns > 0 && output_columns >= total_header_width)
{
/* While there is still excess width... */
while (width_total > output_columns)
{
double max_ratio = 0;
int worst_col = -1;
/*
* Find column that has the highest ratio of its maximum
* width compared to its average width. This tells us which
* column will produce the fewest wrapped values if shortened.
* width_wrap starts as equal to max_width.
*/
for (i = 0; i < col_count; i++)
{
if (width_average[i] && width_wrap[i] > width_header[i])
{
/* Penalize wide columns by +1% of their width (0.01) */
double ratio = (double)width_wrap[i] / width_average[i] +
max_width[i] * 0.01;
if (ratio > max_ratio)
{
max_ratio = ratio;
worst_col = i;
}
}
}
/* Exit loop if we can't squeeze any more. */
if (worst_col == -1)
break;
/* Decrease width of target column by one. */
width_wrap[worst_col]--;
width_total--;
}
}
}
/* time to output */
if (opt->start_table) if (opt->start_table)
{ {
/* print title */ /* print title */
if (title && !opt_tuples_only) if (title && !opt_tuples_only)
{ {
/* Get width & height */ int width, height;
int height;
pg_wcssize((unsigned char *) title, strlen(title), encoding, &tmp, &height, NULL); pg_wcssize((unsigned char *) title, strlen(title), encoding, &width, &height, NULL);
if (tmp >= total_w) if (width >= width_total)
fprintf(fout, "%s\n", title); fprintf(fout, "%s\n", title); /* Aligned */
else else
fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title); fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", title); /* Centered */
} }
/* print headers */ /* print headers */
if (!opt_tuples_only) if (!opt_tuples_only)
{ {
int cols_todo; int more_col_wrapping;
int line_count; int curr_nl_line;
if (opt_border == 2) if (opt_border == 2)
_print_horizontal_line(col_count, widths, opt_border, fout); _print_horizontal_line(col_count, width_wrap, opt_border, fout);
for (i = 0; i < col_count; i++) for (i = 0; i < col_count; i++)
pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]); pg_wcsformat((unsigned char *) headers[i], strlen(headers[i]),
encoding, col_lineptrs[i], max_nl_lines[i]);
cols_todo = col_count; more_col_wrapping = col_count;
line_count = 0; curr_nl_line = 0;
memset(complete, 0, col_count * sizeof(int)); memset(header_done, false, col_count * sizeof(bool));
while (cols_todo) while (more_col_wrapping)
{ {
if (opt_border == 2) if (opt_border == 2)
fprintf(fout, "|%c", line_count ? '+' : ' '); fprintf(fout, "|%c", curr_nl_line ? '+' : ' ');
else if (opt_border == 1) else if (opt_border == 1)
fputc(line_count ? '+' : ' ', fout); fputc(curr_nl_line ? '+' : ' ', fout);
for (i = 0; i < col_count; i++) for (i = 0; i < col_count; i++)
{ {
unsigned int nbspace; unsigned int nbspace;
struct lineptr *this_line = col_lineptrs[i] + line_count; struct lineptr *this_line = col_lineptrs[i] + curr_nl_line;
if (!complete[i]) if (!header_done[i])
{ {
nbspace = widths[i] - this_line->width; nbspace = width_wrap[i] - this_line->width;
/* centered */ /* centered */
fprintf(fout, "%-*s%s%-*s", fprintf(fout, "%-*s%s%-*s",
nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, ""); nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");
if (line_count == (heights[i] - 1) || !(this_line + 1)->ptr) if (!(this_line + 1)->ptr)
{ {
cols_todo--; more_col_wrapping--;
complete[i] = 1; header_done[i] = 1;
} }
} }
else else
fprintf(fout, "%*s", widths[i], ""); fprintf(fout, "%*s", width_wrap[i], "");
if (i < col_count - 1) if (i < col_count - 1)
{ {
if (opt_border == 0) if (opt_border == 0)
fputc(line_count ? '+' : ' ', fout); fputc(curr_nl_line ? '+' : ' ', fout);
else else
fprintf(fout, " |%c", line_count ? '+' : ' '); fprintf(fout, " |%c", curr_nl_line ? '+' : ' ');
} }
} }
line_count++; curr_nl_line++;
if (opt_border == 2) if (opt_border == 2)
fputs(" |", fout); fputs(" |", fout);
else if (opt_border == 1) else if (opt_border == 1)
fputc(' ', fout);; fputc(' ', fout);
fputc('\n', fout); fputc('\n', fout);
} }
_print_horizontal_line(col_count, widths, opt_border, fout); _print_horizontal_line(col_count, width_wrap, opt_border, fout);
} }
} }
/* print cells */ /* print cells, one loop per row */
for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count) for (i = 0, ptr = cells; *ptr; i += col_count, ptr += col_count)
{ {
int j; bool more_lines;
int cols_todo = col_count;
int line_count; /* Number of lines output so far in row */
if (cancel_pressed) if (cancel_pressed)
break; break;
/*
* Format each cell. Format again, it is a numeric formatting locale
* (e.g. 123,456 vs. 123456)
*/
for (j = 0; j < col_count; j++) for (j = 0; j < col_count; j++)
pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], heights[j]);
line_count = 0;
memset(complete, 0, col_count * sizeof(int));
while (cols_todo)
{ {
/* beginning of line */ pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, col_lineptrs[j], max_nl_lines[j]);
curr_nl_line[j] = 0;
if (opt_numeric_locale && opt_align[j % col_count] == 'r')
{
char *my_cell;
my_cell = format_numeric_locale((char *) col_lineptrs[j]->ptr);
strcpy((char *) col_lineptrs[j]->ptr, my_cell); /* Buffer IS large
* enough... now */
free(my_cell);
}
}
memset(bytes_output, 0, col_count * sizeof(int));
/*
* Each time through this loop, one display line is output.
* It can either be a full value or a partial value if embedded
* newlines exist or if 'format=wrapping' mode is enabled.
*/
do
{
more_lines = false;
/* left border */
if (opt_border == 2) if (opt_border == 2)
fputs("| ", fout); fputs("| ", fout);
else if (opt_border == 1) else if (opt_border == 1)
fputc(' ', fout); fputc(' ', fout);
/* for each column */
for (j = 0; j < col_count; j++) for (j = 0; j < col_count; j++)
{ {
struct lineptr *this_line = col_lineptrs[j] + line_count; /* We have a valid array element, so index it */
bool finalspaces = (opt_border == 2 || j != col_count - 1); struct lineptr *this_line = &col_lineptrs[j][curr_nl_line[j]];
int bytes_to_output, chars_to_output = width_wrap[j];
if (complete[j]) /* Just print spaces... */ /* Past newline lines so pad for other columns */
{ if (!this_line->ptr)
if (finalspaces) fprintf(fout, "%*s", width_wrap[j], "");
fprintf(fout, "%*s", widths[j], "");
}
else else
{ {
/* content */ /* Get strlen() of the width_wrap character */
if (opt_align[j] == 'r') bytes_to_output = strlen_max_width(this_line->ptr +
{ bytes_output[j], &chars_to_output, encoding);
if (opt_numeric_locale)
{
/* /*
* Assumption: This code used only on strings * If we exceeded width_wrap, it means the display width
* without multibyte characters, otherwise * of a single character was wider than our target width.
* this_line->width < strlen(this_ptr) and we get * In that case, we have to pretend we are only printing
* an overflow * the target display width and make the best of it.
*/ */
char *my_cell = format_numeric_locale((char *) this_line->ptr); if (chars_to_output > width_wrap[j])
chars_to_output = width_wrap[j];
fprintf(fout, "%*s%s", if (opt_align[j] == 'r') /* Right aligned cell */
(int) (widths[i % col_count] - strlen(my_cell)), "",
my_cell);
free(my_cell);
}
else
fprintf(fout, "%*s%s",
widths[j] - this_line->width, "",
this_line->ptr);
}
else
fprintf(fout, "%-s%*s", this_line->ptr,
finalspaces ? (widths[j] - this_line->width) : 0, "");
/* If at the right height, done this col */
if (line_count == heights[j] - 1 || !this_line[1].ptr)
{ {
complete[j] = 1; /* spaces first */
cols_todo--; fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
fprintf(fout, "%.*s", bytes_to_output,
this_line->ptr + bytes_output[j]);
}
else /* Left aligned cell */
{
/* spaces second */
fprintf(fout, "%.*s", bytes_to_output,
this_line->ptr + bytes_output[j]);
fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
}
bytes_output[j] += bytes_to_output;
/* Do we have more text to wrap? */
if (*(this_line->ptr + bytes_output[j]) != 0)
more_lines = true;
else
{
/* Advance to next newline line */
curr_nl_line[j]++;
if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
more_lines = true;
bytes_output[j] = 0;
} }
} }
/* divider */ /* print a divider, middle columns only */
if ((j + 1) % col_count) if ((j + 1) % col_count)
{ {
if (opt_border == 0) if (opt_border == 0)
fputc(' ', fout); fputc(' ', fout);
else if (line_count == 0) /* Next value is beyond past newlines? */
fputs(" | ", fout); else if (col_lineptrs[j+1][curr_nl_line[j+1]].ptr == NULL)
fputs(" ", fout);
/* In wrapping of value? */
else if (bytes_output[j+1] != 0)
fputs(" ; ", fout);
/* After first newline value */
else if (curr_nl_line[j+1] != 0)
fputs(" : ", fout);
else else
fprintf(fout, " %c ", complete[j + 1] ? ' ' : ':'); /* Ordinary line */
fputs(" | ", fout);
} }
} }
/* end of row border */
if (opt_border == 2) if (opt_border == 2)
fputs(" |", fout); fputs(" |", fout);
fputc('\n', fout); fputc('\n', fout);
line_count++;
} } while (more_lines);
} }
if (opt->stop_table) if (opt->stop_table)
{ {
if (opt_border == 2 && !cancel_pressed) if (opt_border == 2 && !cancel_pressed)
_print_horizontal_line(col_count, widths, opt_border, fout); _print_horizontal_line(col_count, width_wrap, opt_border, fout);
/* print footers */ /* print footers */
if (footers && !opt_tuples_only && !cancel_pressed) if (footers && !opt_tuples_only && !cancel_pressed)
@ -722,12 +863,16 @@ print_aligned_text(const char *title, const char *const * headers,
} }
/* clean up */ /* clean up */
free(widths); free(width_header);
free(heights); free(width_average);
free(max_width);
free(width_wrap);
free(max_nl_lines);
free(curr_nl_line);
free(col_lineptrs); free(col_lineptrs);
free(format_space); free(max_bytes);
free(complete); free(header_done);
free(lineptr_list); free(bytes_output);
for (i = 0; i < col_count; i++) for (i = 0; i < col_count; i++)
free(format_buf[i]); free(format_buf[i]);
free(format_buf); free(format_buf);
@ -754,7 +899,6 @@ print_aligned_vertical(const char *title, const char *const * headers,
dheight = 1, dheight = 1,
hformatsize = 0, hformatsize = 0,
dformatsize = 0; dformatsize = 0;
int tmp = 0;
char *divider; char *divider;
unsigned int cell_count = 0; unsigned int cell_count = 0;
struct lineptr *hlineptr, struct lineptr *hlineptr,
@ -779,12 +923,13 @@ print_aligned_vertical(const char *title, const char *const * headers,
/* Find the maximum dimensions for the headers */ /* Find the maximum dimensions for the headers */
for (i = 0; i < col_count; i++) for (i = 0; i < col_count; i++)
{ {
int height, int width,
height,
fs; fs;
pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &tmp, &height, &fs); pg_wcssize((unsigned char *) headers[i], strlen(headers[i]), encoding, &width, &height, &fs);
if (tmp > hwidth) if (width > hwidth)
hwidth = tmp; hwidth = width;
if (height > hheight) if (height > hheight)
hheight = height; hheight = height;
if (fs > hformatsize) if (fs > hformatsize)
@ -799,7 +944,8 @@ print_aligned_vertical(const char *title, const char *const * headers,
for (i = 0, ptr = cells; *ptr; ptr++, i++) for (i = 0, ptr = cells; *ptr; ptr++, i++)
{ {
int numeric_locale_len; int numeric_locale_len;
int height, int width,
height,
fs; fs;
if (opt_align[i % col_count] == 'r' && opt_numeric_locale) if (opt_align[i % col_count] == 'r' && opt_numeric_locale)
@ -807,10 +953,10 @@ print_aligned_vertical(const char *title, const char *const * headers,
else else
numeric_locale_len = 0; numeric_locale_len = 0;
pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &tmp, &height, &fs); pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, &width, &height, &fs);
tmp += numeric_locale_len; width += numeric_locale_len;
if (tmp > dwidth) if (width > dwidth)
dwidth = tmp; dwidth = width;
if (height > dheight) if (height > dheight)
dheight = height; dheight = height;
if (fs > dformatsize) if (fs > dformatsize)
@ -821,8 +967,8 @@ print_aligned_vertical(const char *title, const char *const * headers,
* We now have all the information we need to setup the formatting * We now have all the information we need to setup the formatting
* structures * structures
*/ */
dlineptr = pg_local_malloc(sizeof(*dlineptr) * dheight); dlineptr = pg_local_malloc((sizeof(*dlineptr) + 1) * dheight);
hlineptr = pg_local_malloc(sizeof(*hlineptr) * hheight); hlineptr = pg_local_malloc((sizeof(*hlineptr) + 1) * hheight);
dlineptr->ptr = pg_local_malloc(dformatsize); dlineptr->ptr = pg_local_malloc(dformatsize);
hlineptr->ptr = pg_local_malloc(hformatsize); hlineptr->ptr = pg_local_malloc(hformatsize);
@ -910,7 +1056,7 @@ print_aligned_vertical(const char *title, const char *const * headers,
fprintf(fout, "%-s%*s", hlineptr[line_count].ptr, fprintf(fout, "%-s%*s", hlineptr[line_count].ptr,
hwidth - hlineptr[line_count].width, ""); hwidth - hlineptr[line_count].width, "");
if (line_count == (hheight - 1) || !hlineptr[line_count + 1].ptr) if (!hlineptr[line_count + 1].ptr)
hcomplete = 1; hcomplete = 1;
} }
else else
@ -943,7 +1089,7 @@ print_aligned_vertical(const char *title, const char *const * headers,
dwidth - dlineptr[line_count].width, ""); dwidth - dlineptr[line_count].width, "");
} }
if (line_count == dheight - 1 || !dlineptr[line_count + 1].ptr) if (!dlineptr[line_count + 1].ptr)
dcomplete = 1; dcomplete = 1;
} }
else else
@ -1823,6 +1969,7 @@ printTable(const char *title,
{ {
static const char *default_footer[] = {NULL}; static const char *default_footer[] = {NULL};
FILE *output; FILE *output;
bool is_pager = false;
if (cancel_pressed) if (cancel_pressed)
return; return;
@ -1858,6 +2005,7 @@ printTable(const char *title,
for (ptr = footers; *ptr; ptr++) for (ptr = footers; *ptr; ptr++)
lines++; lines++;
output = PageOutput(lines, opt->pager); output = PageOutput(lines, opt->pager);
is_pager = (output != fout);
} }
else else
output = fout; output = fout;
@ -1866,7 +2014,7 @@ printTable(const char *title,
if (flog) if (flog)
print_aligned_text(title, headers, cells, footers, align, print_aligned_text(title, headers, cells, footers, align,
opt, flog); opt, is_pager, flog);
switch (opt->format) switch (opt->format)
{ {
@ -1879,12 +2027,13 @@ printTable(const char *title,
opt, output); opt, output);
break; break;
case PRINT_ALIGNED: case PRINT_ALIGNED:
case PRINT_WRAPPED:
if (opt->expanded) if (opt->expanded)
print_aligned_vertical(title, headers, cells, footers, align, print_aligned_vertical(title, headers, cells, footers, align,
opt, output); opt, output);
else else
print_aligned_text(title, headers, cells, footers, align, print_aligned_text(title, headers, cells, footers, align,
opt, output); opt, is_pager, output);
break; break;
case PRINT_HTML: case PRINT_HTML:
if (opt->expanded) if (opt->expanded)
@ -1915,8 +2064,7 @@ printTable(const char *title,
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
/* Only close if we used the pager */ if (is_pager)
if (output != fout)
ClosePager(output); ClosePager(output);
} }
@ -2066,3 +2214,38 @@ setDecimalLocale(void)
else else
thousands_sep = "."; thousands_sep = ".";
} }
/*
* Returns the byte length to the end of the specified character
* and number of display characters processed (useful if the string
* is shorter then dpylen).
*/
static int
strlen_max_width(unsigned char *str, int *target_width, int encoding)
{
unsigned char *start = str;
int curr_width = 0;
while (*str && curr_width < *target_width)
{
int char_width = PQdsplen((char *) str, encoding);
/*
* If the display width of the new character causes
* the string to exceed its target width, skip it
* and return. However, if this is the first character
* of the string (*width == 0), we have to accept it.
*/
if (*target_width - curr_width < char_width && curr_width != 0)
break;
str += PQmblen((char *)str, encoding);
curr_width += char_width;
}
*target_width = curr_width;
/* last byte */
return str - start;
}

View File

@ -3,7 +3,7 @@
* *
* Copyright (c) 2000-2008, PostgreSQL Global Development Group * Copyright (c) 2000-2008, PostgreSQL Global Development Group
* *
* $PostgreSQL: pgsql/src/bin/psql/print.h,v 1.35 2008/01/01 19:45:56 momjian Exp $ * $PostgreSQL: pgsql/src/bin/psql/print.h,v 1.36 2008/05/08 17:04:26 momjian Exp $
*/ */
#ifndef PRINT_H #ifndef PRINT_H
#define PRINT_H #define PRINT_H
@ -21,6 +21,7 @@ enum printFormat
PRINT_NOTHING = 0, /* to make sure someone initializes this */ PRINT_NOTHING = 0, /* to make sure someone initializes this */
PRINT_UNALIGNED, PRINT_UNALIGNED,
PRINT_ALIGNED, PRINT_ALIGNED,
PRINT_WRAPPED,
PRINT_HTML, PRINT_HTML,
PRINT_LATEX, PRINT_LATEX,
PRINT_TROFF_MS PRINT_TROFF_MS
@ -47,6 +48,8 @@ typedef struct _printTableOpt
* decimal marker */ * decimal marker */
char *tableAttr; /* attributes for HTML <table ...> */ char *tableAttr; /* attributes for HTML <table ...> */
int encoding; /* character encoding */ int encoding; /* character encoding */
int env_columns; /* $COLUMNS on psql start, 0 is unset */
int columns; /* target width for wrapped format */
} printTableOpt; } printTableOpt;

View File

@ -3,7 +3,7 @@
* *
* Copyright (c) 2000-2008, PostgreSQL Global Development Group * Copyright (c) 2000-2008, PostgreSQL Global Development Group
* *
* $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.146 2008/01/01 19:45:56 momjian Exp $ * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.147 2008/05/08 17:04:26 momjian Exp $
*/ */
#include "postgres_fe.h" #include "postgres_fe.h"
@ -147,6 +147,8 @@ main(int argc, char *argv[])
pset.popt.topt.start_table = true; pset.popt.topt.start_table = true;
pset.popt.topt.stop_table = true; pset.popt.topt.stop_table = true;
pset.popt.default_footer = true; pset.popt.default_footer = true;
/* We must get COLUMNS here before readline() sets it */
pset.popt.topt.env_columns = getenv("COLUMNS") ? atoi(getenv("COLUMNS")) : 0;
pset.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout))); pset.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout)));