/*
* 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"));
}
syntax highlighted by Code2HTML, v. 0.9.1