/* * vii - buffer and display output * Copyright (C) 1991-1995, 1999, 2005 Peter Miller * * 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 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * * MANIFEST: functions to provide consistent -Help behaviour */ #include <ac/ctype.h> #include <ac/stdarg.h> #include <ac/stdio.h> #include <ac/stdlib.h> #include <ac/string.h> #include <ac/unistd.h> #include <arglex.h> #include <error.h> #include <help.h> #include <mem.h> #include <option.h> #include <str.h> #include <trace.h> #include <version_stmp.h> #define PAIR(a, b) ((a) * 256 + (b)) static FILE *fp; static char *pager; static char *cr[] = { "\\*(n) version \\*(v)", ".br", "Copyright (C) \\*(Y) Peter Miller", "", "The \\*(n) program comes with ABSOLUTELY NO WARRANTY;", "for details use the '\\*(n) -VERSion Warranty' command.", "The \\*(n) program is free software, and you are welcome to", "redistribute it under certain conditions;", "for details use the '\\*(n) -VERSion Redistribution' command.", }; static char *au[] = { ".nf", "Peter Miller E-Mail: millerp@canb.auug.org.au", "/\\e/\\e* WWW: http://www.canb.auug.org.au/~millerp/", ".fi", }; static char *so_o__rules[] = { #include <../man1/o__rules.h> }; static char *so_z_exit[] = { #include <../man1/z_exit.h> }; static char *so_copyright[] = { ".SH COPYRIGHT", ".so cr", ".SH AUTHOR", ".so au", }; typedef struct so_list_ty so_list_ty; struct so_list_ty { char *name; char **text; int length; }; static so_list_ty so_list[] = { { "o__rules.so", so_o__rules, SIZEOF(so_o__rules) }, { "z_exit.so", so_z_exit, SIZEOF(so_z_exit) }, { "z_name.so", 0, 0 }, { "copyright.so", so_copyright, SIZEOF(so_copyright) }, { "../doc/version.so", 0, 0 }, { "cr", cr, SIZEOF(cr), }, { "au", au, SIZEOF(au), }, }; static int ocol; static int icol; static int fill; /* true if currently filling */ static int in; /* current indent */ static int ll; /* line length */ static long roff_line; static char *roff_file; static int TP_line; static void pager_error _((void)); static void pager_error() { nfatal("write %s", (pager ? pager : "standard output")); } static void emit _((int)); static void emit(c) int c; { switch (c) { case ' ': icol++; break; case '\t': icol = ((icol / 8) + 1) * 8; break; case '\n': fputc('\n', fp); fflush(fp); icol = 0; ocol = 0; break; default: if (!isprint(c)) break; while (((ocol / 8) + 1) * 8 <= icol && ocol + 1 < icol) { fputc('\t', fp); ocol = ((ocol / 8) + 1) * 8; } while (ocol < icol) { fputc(' ', fp); ++ocol; } fputc(c, fp); ++icol; ++ocol; break; } if (ferror(fp)) pager_error(); } static void emit_word _((char *, long)); static void emit_word(buf, len) char *buf; long len; { if (len <= 0) return; /* * if this line is not yet indented, indent it */ if (!ocol && !icol) icol = in; /* * if there is already something on this line * and we are in "fill" mode * and this word would cause it to overflow * then wrap the line */ if (ocol && fill && icol + len >= ll) { emit('\n'); icol = in; } if (ocol) emit(' '); while (len-- > 0) emit(*buf++); } static void br _((void)); static void br() { if (ocol) emit('\n'); } static void sp _((void)); static void sp() { br(); emit('\n'); } static void interpret_line_of_words _((char *)); static void interpret_line_of_words(line) char *line; { /* * if not filling, * pump the line out literrally. */ if (!fill) { if (!ocol && !icol) icol = in; while (*line) emit(*line++); emit('\n'); return; } /* * in fill mode, a blank line means * finish the paragraph and emit a blank line */ if (!*line) { sp(); return; } /* * break the line into space-separated words * and emit each individually */ while (*line) { char *start; while (isspace(*line)) ++line; if (!*line) break; start = line; while (*line && !isspace(*line)) ++line; emit_word(start, line - start); /* * extra space at end of sentences */ if ( (line[-1] == '.' || line[-1] == '?') && ( !line[0] || ( line[0] == ' ' && (!line[1] || line[1] == ' ') ) ) ) emit(' '); } } static void roff_error _((char *, ...)); static void roff_error(s sva_last) char *s; sva_last_decl { string_ty *buffer; va_list ap; sva_init(ap, s); buffer = str_vformat(s, ap); va_end(ap); fatal ( "%s: %ld: %S", (roff_file ? roff_file : "(noname)"), roff_line, buffer ); } static void get_name _((char **, char *)); static void get_name(lp, name) char **lp; char *name; { char *line; line = *lp; if (*line == '('/*)*/) { ++line; if (*line) { name[0] = *line++; if (*line) { name[1] = *line++; name[2] = 0; } else name[1] = 0; } else name[0] = 0; } else if (*line) { name[0] = *line++; name[1] = 0; } else name[0] = 0; *lp = line; } typedef struct string_reg_ty string_reg_ty; struct string_reg_ty { char *name; char *value; }; static long string_reg_count; static string_reg_ty *string_reg; static char *string_find _((char *)); static char * string_find(name) char *name; { long j; for (j = 0; j < string_reg_count; ++j) { string_reg_ty *srp; srp = &string_reg[j]; if (!strcmp(name, srp->name)) return srp->value; } return 0; } static char *numreg_find _((char *)); static char * numreg_find(name) char *name; { return 0; } static void roff_prepro _((char *, char *)); static void roff_prepro(buffer, line) char *buffer; char *line; { char *bp; char *value; char name[4]; bp = buffer; while (*line) { int c = *line++; if (c != '\\') { *bp++ = c; continue; } c = *line++; if (!c) { roff_error("can't do escaped end-of-line"); break; } switch (c) { default: roff_error("unknown \\%c inline directive", c); break; case '%': /* word break info */ break; case '*': /* inline string */ get_name(&line, name); value = string_find(name); if (value) { while (*value) *bp++ = *value++; } break; case 'n': /* inline number register */ get_name(&line, name); value = numreg_find(name); if (value) { while (*value) *bp++ = *value++; } break; case 'e': case '\\': *bp++ = '\\'; break; case '-': *bp++ = '-'; break; case 'f': /* ignore font directives */ get_name(&line, name); break; case '&': case '|': /* ignore weird space directives */ break; } } *bp = 0; } static void interpret_text _((char *)); static void interpret_text(line) char *line; { char buffer[1000]; roff_prepro(buffer, line); interpret_line_of_words(buffer); if (TP_line) { if (icol >= 15) br(); else icol = 15; TP_line = 0; in = 16; } } static void roff_sub _((char *, int, char **)); static void roff_sub(buffer, argc, argv) char *buffer; int argc; char **argv; { int j; char *bp; long len; bp = buffer; for (j = 0; j < argc; ++j) { len = strlen(argv[j]); if (j) *bp++ = ' '; memcpy(bp, argv[j], len); bp += len; } *bp = 0; } static void interpret_text_args _((int, char **)); static void interpret_text_args(argc, argv) int argc; char **argv; { char buffer[1000]; roff_sub(buffer, argc, argv); interpret_text(buffer); } static void concat_text_args _((int, char **)); static void concat_text_args(argc, argv) int argc; char **argv; { int j; char *bp; long len; char buffer[1000]; bp = buffer; for (j = 0; j < argc; ++j) { len = strlen(argv[j]); if ((bp - buffer) + len + 1 >= sizeof(buffer)) break; memcpy(bp, argv[j], len); bp += len; } *bp = 0; interpret_text(buffer); } static void interpret _((char **, int)); /* forward */ static void so _((int, char **)); static void so(argc, argv) int argc; char **argv; { so_list_ty *sop; if (argc != 1) { roff_error(".so requires one argument"); return; } for (sop = so_list; sop < ENDOF(so_list); ++sop) { if (!strcmp(sop->name, argv[0])) { interpret(sop->text, sop->length); return; } } roff_error("\".so %s\" not known", argv[0]); } static void lf _((int, char **)); static void lf(argc, argv) int argc; char **argv; { if (roff_file) mem_free(roff_file); if (argc >= 1) roff_line = atol(argv[0]) - 1; else roff_line = 0; if (argc >= 2) roff_file = mem_copy_string(argv[1]); else roff_file = 0; } static void ds_guts _((char *, char *)); static void ds_guts(name, value) char *name; char *value; { long j; string_reg_ty *srp; size_t nbytes; for (j = 0; j < string_reg_count; ++j) { srp = &string_reg[j]; if (!strcmp(name, srp->name)) { mem_free(srp->value); srp->value = mem_copy_string(value); return; } } nbytes = (string_reg_count + 1) * sizeof(string_reg_ty); string_reg = mem_change_size(string_reg, nbytes); srp = &string_reg[string_reg_count++]; srp->name = mem_copy_string(name); srp->value = mem_copy_string(value); } static void ds _((int, char **)); static void ds(argc, argv) int argc; char **argv; { #if 1 /* * ignore .ds directives * values already set appropriately */ #else char buf1[1000]; char buf2[1000]; if (!argc) return; roff_sub(buf1, argc - 1, argv + 1); roff_prepro(buf2, buf1); ds_guts(argv[0], buf2); #endif } static void dot_in _((int, char**)); static void dot_in(argc, argv) int argc; char **argv; { if (argc < 1) return; switch (argv[0][0]) { case '-': in -= atoi(argv[0] + 1); break; case '+': in += atoi(argv[0] + 1); break; default: in = atoi(argv[0] + 1); break; } if (in < 0) in = 0; } static void interpret _((char **, int)); /* forward */ static void interpret_control _((char *)); static void interpret_control(line) char *line; { int c1, c2; int argc; char *argv[20]; char temp[1000]; char *cp; /* * find the directive name */ line++; while (isspace(*line)) ++line; if (*line) c1 = *line++; else c1 = ' '; if (*line) c2 = *line++; else c2 = ' '; /* * break the line into space-separated arguments */ argc = 0; cp = temp; while (argc < SIZEOF(argv)) { int quoting; while (isspace(*line)) ++line; if (!*line) break; argv[argc++] = cp; quoting = 0; while (*line) { if (*line == '"') { quoting = !quoting; ++line; continue; } if (!quoting && isspace(*line)) break; *cp++ = *line++; } *cp++ = 0; if (!*line) break; } /* * now do something with it */ switch (PAIR(c1, c2)) { case PAIR('n', 'e'): /* ignore the space needed directive */ break; case PAIR('f', 't'): /* ignore the font directive */ break; case PAIR('i', 'n'): dot_in(argc, argv); break; case PAIR('I', ' '): case PAIR('I', 'R'): case PAIR('I', 'B'): case PAIR('R', ' '): case PAIR('R', 'I'): case PAIR('R', 'B'): case PAIR('B', ' '): case PAIR('B', 'I'): case PAIR('B', 'R'): concat_text_args(argc, argv); break; case PAIR('n', 'f'): br(); fill = 0; break; case PAIR('f', 'i'): br(); fill = 1; break; case PAIR('t', 'a'): /* ignore tab directive */ break; case PAIR('b', 'r'): br(); break; case PAIR('s', 'p'): sp(); break; case PAIR('I', 'P'): sp(); emit(' '); emit(' '); break; case PAIR('P', 'P'): in = 8; sp(); break; case PAIR('T', 'H'): break; case PAIR('T', 'P'): in = 8; sp(); TP_line = 1; break; case PAIR('S', 'H'): in = 0; sp(); interpret_text_args(argc, argv); br(); in = 8; break; case PAIR('S', 'S'): in = 4; sp(); interpret_text_args(argc, argv); br(); in = 8; break; case PAIR('s', 'o'): so(argc, argv); break; case PAIR('l', 'f'): lf(argc, argv); break; case PAIR('R', 'S'): in += 8; break; case PAIR('R', 'E'): in -= 8; if (in < 0) in = 0; break; case PAIR('d', 's'): ds(argc, argv); break; case PAIR('r', /*(*/')'): cp = string_find(/*(*/"R)"); if (!cp) cp = ""; if (strcmp(cp, "no") != 0) { static char *macro[] = { ".PP", "See also", ".IR \\*(n) (1)", "for options common to all \\*(n) commands.", }; interpret(macro, SIZEOF(macro)); } break; default: roff_error("formatting directive \".%c%c\" unknown", c1, c2); break; } } static void interpret _((char **, int)); static void interpret(text, text_len) char **text; int text_len; { int j; long hold_line; char *hold_file; /* * save position */ trace(("interpret()\n{\n"/*}*/)); hold_line = roff_line; hold_file = roff_file ? mem_copy_string(roff_file) : (char *)0; /* * interpret the text */ for (j = 0; j < text_len; ++j) { char *s; s = text[j]; if (*s == '.' || *s == '\'') interpret_control(s); else interpret_text(s); ++roff_line; if (ferror(fp)) pager_error(); } /* * restore position */ if (roff_file) mem_free(roff_file); roff_line = hold_line; roff_file = hold_file; trace((/*{*/"}\n")); } void help(text, text_len, usage) char **text; int text_len; void (*usage)_((void)); { /* * collect the rest of thge command line, * if necessary */ trace(("help(text = %08lX, text_len = %d, usage = %08lX)\n{\n"/*}*/, text, text_len, usage)); if (usage && arglex() != arglex_token_eoln) { error ( "misplaced \"%s\" command line argument", arglex_value.alv_string ); usage(); } /* * if output is to the terminal, * send the output through a paginator */ if (isatty(0) && isatty(1)) { pager = getenv("PAGER"); if (!pager) pager = "more"; } else pager = 0; /* * open the paginator */ if (pager) { fp = popen(pager, "w"); if (!fp) { nerror("%s", pager); pager = 0; fp = stdout; } } else fp = stdout; /* * initialize the state of the interpreter */ ds_guts(/*(*/"n)", option_progname_get()); ds_guts(/*(*/"v)", version_stamp()); ds_guts(/*(*/"Y)", copyright_years()); ll = option_page_width_get() - 1; in = 0; fill = 1; ocol = 0; icol = 0; lf(0, 0); TP_line = 0; /* * do what they asked */ interpret(text, text_len); br(); /* * close the paginator */ if (pager) pclose(fp); trace((/*{*/"}\n")); }