X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Fascii.c;h=29d7b76b2940d76dbc0af108827922785f8e71ce;hb=ff59ee87992b440aab8083ee041f9aecd2ce68ca;hp=bafe74acce9185eae9164e52d0a0cee78bd46f25;hpb=52b9fca3a729eaef7d13469029e4391aa033a659;p=pspp-builds.git diff --git a/src/output/ascii.c b/src/output/ascii.c index bafe74ac..29d7b76b 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -1,31 +1,30 @@ -/* PSPP - computes sample statistics. - Copyright (C) 1997-9, 2000 Free Software Foundation, Inc. - Written by Ben Pfaff . +/* PSPP - a program for statistical analysis. + Copyright (C) 1997-9, 2000, 2007 Free Software Foundation, Inc. - This program 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 2 of the - License, or (at your option) any later version. + This program 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 of the License, or + (at your option) any later version. - This program 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. + This program 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 this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ + along with this program. If not, see . */ #include #include #include #include +#include #include -#include -#include +#include +#include +#include #include #include #include @@ -35,6 +34,7 @@ #include "error.h" #include "minmax.h" #include "output.h" +#include "xalloc.h" #include "gettext.h" #define _(msgid) gettext (msgid) @@ -42,19 +42,24 @@ /* ASCII driver options: (defaults listed first) output-file="pspp.list" + append=no|yes If output-file exists, append to it? + chart-files="pspp-#.png" Name used for charts. + chart-type=png Format of charts (use "none" to disable). + paginate=on|off Formfeeds are desired? tab-width=8 Width of a tab; 0 to not use tabs. - + headers=on|off Put headers at top of page? emphasis=bold|underline|none Style to use for emphasis. - length=66 - width=130 + length=66|auto + width=79|auto squeeze=off|on Squeeze multiple newlines into exactly one. top-margin=2 bottom-margin=2 box[x]="strng" Sets box character X (X in base 4: 0-3333). + init="string" Set initialization string. */ /* Disable messages by failed range checks. */ @@ -76,7 +81,7 @@ enum #define ATTR_BOX 0x200 /* Line drawing character. */ /* A line of text. */ -struct line +struct line { unsigned short *chars; /* Characters and attributes. */ int char_cnt; /* Length. */ @@ -84,7 +89,7 @@ struct line }; /* How to emphasize text. */ -enum emphasis_style +enum emphasis_style { EMPH_BOLD, /* Overstrike for bold. */ EMPH_UNDERLINE, /* Overstrike for underlining. */ @@ -97,32 +102,42 @@ struct ascii_driver_ext struct pool *pool; /* User parameters. */ + bool append; /* Append if output-file already exists? */ bool headers; /* Print headers at top of page? */ bool paginate; /* Insert formfeeds? */ bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */ enum emphasis_style emphasis; /* How to emphasize text. */ int tab_width; /* Width of a tab; 0 not to use tabs. */ + const char *chart_type; /* Type of charts to output; NULL for none. */ + const char *chart_file_name; /* Name of files used for charts. */ + bool auto_width; /* Use viewwidth as page width? */ + bool auto_length; /* Use viewlength as page width? */ int page_length; /* Page length before subtracting margins. */ int top_margin; /* Top margin in lines. */ int bottom_margin; /* Bottom margin in lines. */ char *box[LNS_COUNT]; /* Line & box drawing characters. */ + char *init; /* Device initialization string. */ /* Internal state. */ char *file_name; /* Output file name. */ FILE *file; /* Output file. */ + bool reported_error; /* Reported file open error? */ int page_number; /* Current page number. */ struct line *lines; /* Page content. */ int line_cap; /* Number of lines allocated. */ + int chart_cnt; /* Number of charts so far. */ }; +static void ascii_flush (struct outp_driver *); static int get_default_box_char (size_t idx); +static bool update_page_size (struct outp_driver *, bool issue_error); static bool handle_option (struct outp_driver *this, const char *key, const struct string *val); static bool -ascii_open_driver (struct outp_driver *this, const struct string *options) +ascii_open_driver (struct outp_driver *this, struct substring options) { struct ascii_driver_ext *x; int i; @@ -135,55 +150,45 @@ ascii_open_driver (struct outp_driver *this, const struct string *options) this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE; this->ext = x = pool_create_container (struct ascii_driver_ext, pool); + x->append = false; x->headers = true; x->paginate = true; x->squeeze_blank_lines = false; x->emphasis = EMPH_BOLD; x->tab_width = 8; + x->chart_file_name = pool_strdup (x->pool, "pspp-#.png"); + x->chart_type = pool_strdup (x->pool, "png"); + x->auto_width = false; + x->auto_length = false; x->page_length = 66; x->top_margin = 2; x->bottom_margin = 2; for (i = 0; i < LNS_COUNT; i++) x->box[i] = NULL; + x->init = NULL; x->file_name = pool_strdup (x->pool, "pspp.list"); x->file = NULL; + x->reported_error = false; x->page_number = 0; x->lines = NULL; x->line_cap = 0; + x->chart_cnt = 0; if (!outp_parse_options (options, handle_option, this)) goto error; - x->file = pool_fopen (x->pool, x->file_name, "w"); - if (x->file == NULL) - { - error (0, errno, _("ascii: opening output file \"%s\""), x->file_name); - goto error; - } - - this->length = x->page_length - x->top_margin - x->bottom_margin - 1; - if (x->headers) - this->length -= 3; - - if (this->width < 59 || this->length < 15) - { - error (0, 0, - _("ascii: page excluding margins and headers " - "must be at least 59 characters wide by 15 lines long, but as " - "configured is only %d characters by %d lines"), - this->width, this->length); - return false; - } + if (!update_page_size (this, true)) + goto error; for (i = 0; i < LNS_COUNT; i++) - if (x->box[i] == NULL) + if (x->box[i] == NULL) { char s[2]; s[0] = get_default_box_char (i); s[1] = '\0'; x->box[i] = pool_strdup (x->pool, s); } - + return true; error: @@ -223,16 +228,52 @@ get_default_box_char (size_t idx) } } +/* Re-calculates the page width and length based on settings, + margins, and, if "auto" is set, the size of the user's + terminal window or GUI output window. */ +static bool +update_page_size (struct outp_driver *this, bool issue_error) +{ + struct ascii_driver_ext *x = this->ext; + int margins = x->top_margin + x->bottom_margin + 1 + (x->headers ? 3 : 0); + + if (x->auto_width) + this->width = settings_get_viewwidth (); + if (x->auto_length) + x->page_length = settings_get_viewlength (); + + this->length = x->page_length - margins; + + if (this->width < 59 || this->length < 15) + { + if (issue_error) + error (0, 0, + _("ascii: page excluding margins and headers " + "must be at least 59 characters wide by 15 lines long, but " + "as configured is only %d characters by %d lines"), + this->width, this->length); + if (this->width < 59) + this->width = 59; + if (this->length < 15) + { + this->length = 15; + x->page_length = this->length + margins; + } + return false; + } + + return true; +} + static bool ascii_close_driver (struct outp_driver *this) { struct ascii_driver_ext *x = this->ext; - - if (fn_close (x->file_name, x->file) != 0) - error (0, errno, _("ascii: closing output file \"%s\""), x->file_name); + + ascii_flush (this); pool_detach_file (x->pool, x->file); pool_destroy (x->pool); - + return true; } @@ -240,29 +281,33 @@ ascii_close_driver (struct outp_driver *this) enum { boolean_arg, - string_arg, + emphasis_arg, nonneg_int_arg, - pos_int_arg, - output_file_arg + page_size_arg, + string_arg }; -static struct outp_option option_tab[] = +static const struct outp_option option_tab[] = { {"headers", boolean_arg, 0}, {"paginate", boolean_arg, 1}, {"squeeze", boolean_arg, 2}, + {"append", boolean_arg, 3}, - {"emphasis", string_arg, 3}, - - {"output-file", output_file_arg, 0}, + {"emphasis", emphasis_arg, 0}, - {"length", pos_int_arg, 0}, - {"width", pos_int_arg, 1}, + {"length", page_size_arg, 0}, + {"width", page_size_arg, 1}, {"top-margin", nonneg_int_arg, 0}, {"bottom-margin", nonneg_int_arg, 1}, {"tab-width", nonneg_int_arg, 2}, + {"output-file", string_arg, 0}, + {"chart-files", string_arg, 1}, + {"chart-type", string_arg, 2}, + {"init", string_arg, 3}, + {NULL, 0, 0}, }; @@ -274,7 +319,7 @@ handle_option (struct outp_driver *this, const char *key, int subcat; const char *value; - value = ds_c_str (val); + value = ds_cstr (val); if (!strncmp (key, "box[", 4)) { char *tail; @@ -298,43 +343,61 @@ handle_option (struct outp_driver *this, const char *key, case -1: error (0, 0, _("ascii: unknown parameter `%s'"), key); break; - case output_file_arg: - x->file_name = pool_strdup (x->pool, value); - break; - case pos_int_arg: + case page_size_arg: { char *tail; int arg; - errno = 0; - arg = strtol (value, &tail, 0); - if (arg < 1 || errno == ERANGE || *tail) - { - error (0, 0, _("ascii: positive integer required as `%s' value"), - key); - break; - } - switch (subcat) - { - case 0: - x->page_length = arg; - break; - case 1: - this->width = arg; - break; - default: - abort (); - } + if (ss_equals_case (ds_ss (val), ss_cstr ("auto"))) + { + if (!(this->device & OUTP_DEV_SCREEN)) + { + /* We only let `screen' devices have `auto' + length or width because output to such devices + is flushed before each new command. Resizing + a device in the middle of output seems like a + bad idea. */ + error (0, 0, _("ascii: only screen devices may have `auto' " + "length or width")); + } + else if (subcat == 0) + x->auto_length = true; + else + x->auto_width = true; + } + else + { + errno = 0; + arg = strtol (value, &tail, 0); + if (arg < 1 || errno == ERANGE || *tail) + { + error (0, 0, _("ascii: positive integer required as " + "`%s' value"), + key); + break; + } + switch (subcat) + { + case 0: + x->page_length = arg; + break; + case 1: + this->width = arg; + break; + default: + NOT_REACHED (); + } + } } break; - case string_arg: + case emphasis_arg: if (!strcmp (value, "bold")) x->emphasis = EMPH_BOLD; else if (!strcmp (value, "underline")) x->emphasis = EMPH_UNDERLINE; else if (!strcmp (value, "none")) x->emphasis = EMPH_NONE; - else + else error (0, 0, _("ascii: `emphasis' value must be `bold', " "`underline', or `none'")); @@ -365,7 +428,7 @@ handle_option (struct outp_driver *this, const char *key, x->tab_width = arg; break; default: - abort (); + NOT_REACHED (); } } break; @@ -394,13 +457,39 @@ handle_option (struct outp_driver *this, const char *key, case 2: x->squeeze_blank_lines = setting; break; + case 3: + x->append = setting; + break; default: - abort (); + NOT_REACHED (); } } break; + case string_arg: + switch (subcat) + { + case 0: + x->file_name = pool_strdup (x->pool, value); + break; + case 1: + if (ds_find_char (val, '#') != SIZE_MAX) + x->chart_file_name = pool_strdup (x->pool, value); + else + error (0, 0, _("`chart-files' value must contain `#'")); + break; + case 2: + if (value[0] != '\0') + x->chart_type = pool_strdup (x->pool, value); + else + x->chart_type = NULL; + break; + case 3: + x->init = pool_strdup (x->pool, value); + break; + } + break; default: - abort (); + NOT_REACHED (); } return true; @@ -412,13 +501,39 @@ ascii_open_page (struct outp_driver *this) struct ascii_driver_ext *x = this->ext; int i; + update_page_size (this, false); + + if (x->file == NULL) + { + x->file = fn_open (x->file_name, x->append ? "a" : "w"); + if (x->file != NULL) + { + pool_attach_file (x->pool, x->file); + if (x->init != NULL) + fputs (x->init, x->file); + } + else + { + /* Report the error to the user and complete + initialization. If we do not finish initialization, + then calls to other driver functions will segfault + later. It would be better to simply drop the driver + entirely, but we do not have a convenient mechanism + for this (yet). */ + if (!x->reported_error) + error (0, errno, _("ascii: opening output file \"%s\""), + x->file_name); + x->reported_error = true; + } + } + x->page_number++; if (this->length > x->line_cap) { x->lines = pool_nrealloc (x->pool, x->lines, this->length, sizeof *x->lines); - for (i = x->line_cap; i < this->length; i++) + for (i = x->line_cap; i < this->length; i++) { struct line *line = &x->lines[i]; line->chars = NULL; @@ -438,24 +553,24 @@ expand_line (struct outp_driver *this, int y, int length) { struct ascii_driver_ext *ext = this->ext; struct line *line = &ext->lines[y]; - if (line->char_cnt < length) + if (line->char_cnt < length) { int x; - if (line->char_cap < length) + if (line->char_cap < length) { line->char_cap = MIN (length * 2, this->width); line->chars = pool_nrealloc (ext->pool, line->chars, - line->char_cap, sizeof *line->chars); + line->char_cap, sizeof *line->chars); } for (x = line->char_cnt; x < length; x++) line->chars[x] = ' '; - line->char_cnt = length; + line->char_cnt = length; } } static void -ascii_line (struct outp_driver *this, +ascii_line (struct outp_driver *this, int x0, int y0, int x1, int y1, enum outp_line_style top, enum outp_line_style left, enum outp_line_style bottom, enum outp_line_style right) @@ -478,16 +593,25 @@ ascii_line (struct outp_driver *this, value = ((left << LNS_LEFT) | (right << LNS_RIGHT) | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX); - for (y = y0; y < y1; y++) + for (y = y0; y < y1; y++) { int x; expand_line (this, y, x1); for (x = x0; x < x1; x++) - ext->lines[y].chars[x] = value; + ext->lines[y].chars[x] = value; } } +static void +ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s) +{ + extern struct som_table_class tab_table_class; + + assert (s->class == &tab_table_class); + assert (s->type == SOM_CHART); +} + static void text_draw (struct outp_driver *this, enum outp_font font, @@ -511,7 +635,7 @@ text_draw (struct outp_driver *this, x += width - length; break; default: - abort (); + NOT_REACHED (); } if (y >= this->length || x >= this->width) @@ -527,9 +651,10 @@ text_draw (struct outp_driver *this, ext->lines[y].chars[x++] = *string++ | attr; } -/* Divides the text T->S into lines of width T->H. Sets T->V to the - number of lines necessary. Actually draws the text if DRAW is - true. */ +/* Divides the text T->S into lines of width T->H. Sets *WIDTH + to the maximum width of a line and *HEIGHT to the number of + lines, if those arguments are non-null. Actually draws the + text if DRAW is true. */ static void delineate (struct outp_driver *this, const struct outp_text *text, bool draw, int *width, int *height) @@ -537,7 +662,7 @@ delineate (struct outp_driver *this, const struct outp_text *text, bool draw, int max_width; int height_left; - const char *cp = ls_c_str (&text->string); + const char *cp = ss_data (text->string); max_width = 0; height_left = text->v; @@ -549,7 +674,7 @@ delineate (struct outp_driver *this, const struct outp_text *text, bool draw, const char *end; /* Initially the line is up to text->h characters long. */ - chars_left = ls_end (&text->string) - cp; + chars_left = ss_end (text->string) - cp; if (chars_left == 0) break; line_len = MIN (chars_left, text->h); @@ -560,7 +685,7 @@ delineate (struct outp_driver *this, const struct outp_text *text, bool draw, line_len = end - cp; /* Don't cut off words if it can be avoided. */ - if (cp + line_len < ls_end (&text->string)) + if (cp + line_len < ss_end (text->string)) { size_t space_len = line_len; while (space_len > 0 && !isspace ((unsigned char) cp[space_len])) @@ -568,7 +693,7 @@ delineate (struct outp_driver *this, const struct outp_text *text, bool draw, if (space_len > 0) line_len = space_len; } - + /* Draw text. */ if (draw) text_draw (this, @@ -584,7 +709,7 @@ delineate (struct outp_driver *this, const struct outp_text *text, bool draw, /* Next line. */ cp += line_len; - if (cp < ls_end (&text->string) && isspace ((unsigned char) *cp)) + if (cp < ss_end (text->string) && isspace ((unsigned char) *cp)) cp++; } @@ -607,7 +732,6 @@ ascii_text_draw (struct outp_driver *this, const struct outp_text *t) assert (this->page_open); delineate (this, t, true, NULL, NULL); } - /* ascii_close_page () and support routines. */ @@ -622,20 +746,20 @@ output_line (struct outp_driver *this, const struct line *line, for (length = line->char_cnt; length-- > 0; s++) if (*s & ATTR_BOX) - ds_puts (out, ext->box[*s & 0xff]); + ds_put_cstr (out, ext->box[*s & 0xff]); else { - if (*s & ATTR_EMPHASIS) + if (*s & ATTR_EMPHASIS) { if (ext->emphasis == EMPH_BOLD) { - ds_putc (out, *s); - ds_putc (out, '\b'); + ds_put_char (out, *s); + ds_put_char (out, '\b'); } else if (ext->emphasis == EMPH_UNDERLINE) - ds_puts (out, "_\b"); + ds_put_cstr (out, "_\b"); } - ds_putc (out, *s); + ds_put_char (out, *s); } } @@ -643,22 +767,22 @@ static void append_lr_justified (struct string *out, int width, const char *left, const char *right) { - ds_putc_multiple (out, ' ', width); - if (left != NULL) + ds_put_char_multiple (out, ' ', width); + if (left != NULL) { size_t length = MIN (strlen (left), width); - memcpy (ds_end (out) - width, left, length); + memcpy (ds_end (out) - width, left, length); } if (right != NULL) { size_t length = MIN (strlen (right), width); memcpy (ds_end (out) - length, right, length); } - ds_putc (out, '\n'); + ds_put_char (out, '\n'); } static void -dump_output (struct outp_driver *this, struct string *out) +dump_output (struct outp_driver *this, struct string *out) { struct ascii_driver_ext *x = this->ext; fwrite (ds_data (out), ds_length (out), 1, x->file); @@ -671,69 +795,129 @@ ascii_close_page (struct outp_driver *this) struct ascii_driver_ext *x = this->ext; struct string out; int line_num; - - ds_init (&out, 128); - - ds_putc_multiple (&out, '\n', x->top_margin); + + if (x->file == NULL) + return; + + ds_init_empty (&out); + + ds_put_char_multiple (&out, '\n', x->top_margin); if (x->headers) { char *r1, *r2; - + r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number); r2 = xasprintf ("%s - %s" , version, host_system); - + append_lr_justified (&out, this->width, outp_title, r1); append_lr_justified (&out, this->width, outp_subtitle, r2); - ds_putc (&out, '\n'); - + ds_put_char (&out, '\n'); + free (r1); free (r2); } dump_output (this, &out); - + for (line_num = 0; line_num < this->length; line_num++) { - + /* Squeeze multiple blank lines into a single blank line if requested. */ - if (x->squeeze_blank_lines) + if (x->squeeze_blank_lines) { if (line_num >= x->line_cap) break; if (line_num > 0 && x->lines[line_num].char_cnt == 0 && x->lines[line_num - 1].char_cnt == 0) - continue; + continue; } - - if (line_num < x->line_cap) - output_line (this, &x->lines[line_num], &out); - ds_putc (&out, '\n'); + + if (line_num < x->line_cap) + output_line (this, &x->lines[line_num], &out); + ds_put_char (&out, '\n'); dump_output (this, &out); } - - ds_putc_multiple (&out, '\n', x->bottom_margin); - if (x->paginate) - ds_putc (&out, '\f'); + + ds_put_char_multiple (&out, '\n', x->bottom_margin); + if (x->paginate) + ds_put_char (&out, '\f'); dump_output (this, &out); ds_destroy (&out); } +/* Flushes all output to the user and lets the user deal with it. + This is applied only to output drivers that are designated as + "screen" drivers that the user is interacting with in real + time. */ +static void +ascii_flush (struct outp_driver *this) +{ + struct ascii_driver_ext *x = this->ext; + if (x->file != NULL) + { + if (fn_close (x->file_name, x->file) != 0) + error (0, errno, _("ascii: closing output file \"%s\""), + x->file_name); + pool_detach_file (x->pool, x->file); + x->file = NULL; + } +} + static void -ascii_chart_initialise (struct outp_driver *d UNUSED, struct chart *ch) +ascii_chart_initialise (struct outp_driver *this, struct chart *ch) { - error (0, 0, _("ascii: charts are unsupported by this driver")); - ch->lp = 0; + struct ascii_driver_ext *x = this->ext; + struct outp_text t; + char *text; + + if (x->chart_type == NULL) + return; + + /* Initialize chart. */ + chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt); + if (ch->file_name == NULL) + return; + + /* Mention chart in output. + First advance current position. */ + if (!this->page_open) + outp_open_page (this); + else + { + this->cp_y++; + if (this->cp_y >= this->length) + { + outp_close_page (this); + outp_open_page (this); + } + } + + /* Then write the text. */ + text = xasprintf ("See %s for a chart.", ch->file_name); + t.font = OUTP_FIXED; + t.justification = OUTP_LEFT; + t.string = ss_cstr (text); + t.h = this->width; + t.v = 1; + t.x = 0; + t.y = this->cp_y; + ascii_text_draw (this, &t); + this->cp_y++; + + free (text); } -static void -ascii_chart_finalise (struct outp_driver *d UNUSED, struct chart *ch UNUSED) +static void +ascii_chart_finalise (struct outp_driver *this, struct chart *ch) { - + struct ascii_driver_ext *x = this->ext; + if (x->chart_type != NULL) + chart_finalise_separate (ch); } -struct outp_class ascii_class = +const struct outp_class ascii_class = { "ascii", 0, @@ -743,8 +927,9 @@ struct outp_class ascii_class = ascii_open_page, ascii_close_page, + ascii_flush, - NULL, + ascii_submit, ascii_line, ascii_text_metrics,