WIP: scroll(1) #127

Draft
emma wants to merge 7 commits from pg into main
2 changed files with 84 additions and 38 deletions
Showing only changes of commit 9c1e0d785d - Show all commits

View File

@ -9,10 +9,23 @@
pg \(en paginate pg \(en paginate
.SH SYNOPSIS
pg
.RB ( -p
.RB [ prompt ])
.SH DESCRIPTION .SH DESCRIPTION
Review

Elaborate on pages here.

Elaborate on pages here.
Pg prints standard input to standard output, accepting commands between pages. Pg prints standard input to standard output, accepting commands between pages.
.SH OPTIONS
.B -p
.RS
Replaces the default prompt (": ") with the next argument.
.RE
.SH DIAGNOSTICS .SH DIAGNOSTICS
Pg returns an unsuccessful exit code if the tty couldn't be opened or if pg was Pg returns an unsuccessful exit code if the tty couldn't be opened or if pg was

109
src/pg.c
View File

@ -22,11 +22,14 @@
* NULL */ * NULL */
#include <stdlib.h> /* size_t */ #include <stdlib.h> /* size_t */
#include <string.h> /* strchr(3), strcmp(3), strerror(3), strtok(3) */ #include <string.h> /* strchr(3), strcmp(3), strerror(3), strtok(3) */
#include <unistd.h> /* getopt(3) */
#if !defined EX_OK || !defined EX_SOFTWARE || !defined EX_USAGE #if !defined EX_OK || !defined EX_SOFTWARE || !defined EX_USAGE
# include <sysexits.h> # include <sysexits.h>
#endif #endif
#define CMDLINE_MAX 99+1 /* Maximum length of command line. */
/* Done while looking at plan9ports' src/cmd/p.c. /* Done while looking at plan9ports' src/cmd/p.c.
* Taken from plan9 are the interface (minus p(1)'s shell escaping and the * Taken from plan9 are the interface (minus p(1)'s shell escaping and the
* ability to page multiple files) and the practice of reading /dev/tty to get * ability to page multiple files) and the practice of reading /dev/tty to get
@ -46,48 +49,64 @@ static char *program_name = "pg";
/* Commands start with cmd_. They take an argc and NULL-terminated argv, like /* Commands start with cmd_. They take an argc and NULL-terminated argv, like
* main, and return one of the following RET_ enums as a status. */ * main, and return one of the following RET_ enums as a status. */
enum{ RET_ARGSERROR, RET_OK, RET_NOCMD, RET_REQSHUTDOWN, RET_SOFTWARE }; enum{ RET_OK, RET_REQSHUTDOWN, RET_USAGE, RET_NOCMD, RET_SOFTWARE };
static int page_bytes(FILE *f, size_t l){ /* Page at most l bytes from f without consideration of a buffer (print them to
* stdout). */
static int pg_b_u(FILE *f, size_t l){
int c; int c;
while((c = fgetc(f)) != EOF) while((c = fgetc(f)) != EOF)
if((c = fputc(c, stdout)) == EOF || --l == 0) if((c = fputc(c, stdout)) == EOF || --l == 0)
break; break;
return c == EOF return c;
? RET_REQSHUTDOWN
: RET_OK;
} }
static int page_lines(FILE *f, size_t l){ /* Page at most l lines, which are lengths of characters terminated by nl, from
* f without consideration of a buffer (print them to stdout). */
static int pg_l_u(FILE *f, size_t l, char nl){
int c; int c;
while((c = fgetc(f)) != EOF) while((c = fgetc(f)) != EOF)
if((c = fputc(c, stdout)) == EOF || (l -= (c == '\n')) == 0) if((c = fputc(c, stdout)) == EOF || (l -= (c == nl)) == 0)
break; break;
return c == EOF return c;
? RET_REQSHUTDOWN
: RET_OK;
} }
static int cmd_quit(int argc, char **argv){ return RET_REQSHUTDOWN; } static int cmd_quit(int argc, char **argv){ return RET_REQSHUTDOWN; }
static int cmd_default_page(int argc, char **argv){ static int cmd_default_page(int argc, char **argv){
if(strtok(NULL, whitespace) != NULL) if(argc > 1) /* This shouldn't be possible. */
return RET_ARGSERROR; /* This shouldn't be possible. */ return RET_USAGE;
if(default_page_unit.type == BYTES) if(default_page_unit.type == BYTES)
return page_bytes(input, default_page_unit.quantity); return pg_b_u(input, default_page_unit.quantity) == EOF
? RET_REQSHUTDOWN
: RET_OK;
else if(default_page_unit.type == LINES) else if(default_page_unit.type == LINES)
return page_lines(input, default_page_unit.quantity); return pg_l_u(input, default_page_unit.quantity, '\n') == EOF
? RET_REQSHUTDOWN
: RET_OK;
else else
return RET_SOFTWARE; return RET_SOFTWARE;
} }
static int cmd_page_down_lines(int argc, char **argv){
switch(argc){
case 1:
return cmd_default_page(argc, argv);
case 2: /* not implemented */
default:
fprintf(stderr, "Usage: %s" /*" (lines)"*/ "\n", argv[0]);
return RET_USAGE;
}
}
/* A CmdMap must be NULL-terminated. */ /* A CmdMap must be NULL-terminated. */
static struct CmdMap{ char *name; int (*fn)(int, char **); } cmd_map[] = { static struct CmdMap{ char *name; int (*fn)(int, char **); } cmd_map[] = {
{ "", cmd_default_page }, { "", cmd_default_page },
{ "+", cmd_page_down_lines },
/* don't make the user feel trapped */ /* don't make the user feel trapped */
{ "exit", cmd_quit }, { "q", cmd_quit }, { ":q", cmd_quit }, { "exit", cmd_quit }, { "q", cmd_quit }, { ":q", cmd_quit },
@ -100,6 +119,9 @@ static struct CmdMap{ char *name; int (*fn)(int, char **); } cmd_map[] = {
/* Find and execute the command in the command map, given a corresponding /* Find and execute the command in the command map, given a corresponding
* command line. */ * command line. */
static int cmdline_exec(struct CmdMap *map, char *cmdline){ static int cmdline_exec(struct CmdMap *map, char *cmdline){
/* Command line word splitting is naive and based on whitespace ONLY; no
* fancy quoting or escaping here. Adding that would (ideally) entail
* replacing strtok(3) with something specific to this task. */
static int argc; static int argc;
static char *argv[ARGV_MAX]; static char *argv[ARGV_MAX];
@ -109,7 +131,7 @@ static int cmdline_exec(struct CmdMap *map, char *cmdline){
argv[argc] = cmdline; argv[argc] = cmdline;
argv[++argc] = NULL; argv[++argc] = NULL;
}else{ }else{
while((argv[++argc] = strtok(NULL , whitespace)) != NULL while((argv[++argc] = strtok(NULL, whitespace)) != NULL
&& argc < ARGV_MAX); && argc < ARGV_MAX);
} }
@ -121,49 +143,60 @@ static int cmdline_exec(struct CmdMap *map, char *cmdline){
return RET_NOCMD; return RET_NOCMD;
} }
int usage(char *s){
fprintf(stderr, "Usage: %s (-p [prompt])\n", s);
return EX_USAGE;
}
int main(int argc, char *argv[]){ int main(int argc, char *argv[]){
unsigned char cmd[99+1]; unsigned char cmd[CMDLINE_MAX];
int r;
FILE *t; FILE *t;
input = stdin; input = stdin;
if(argc > 0) if(argc < 1)
program_name = argv[0]; program_name = argv[0];
else{
int c;
if(argc > 1){ while((c = getopt(argc, argv, "p:")) != -1)
fprintf(stderr, "Usage: %s\n", program_name); switch(c){
return EX_USAGE; case 'p': prompt = optarg; break;
default:
return usage(program_name);
}
} }
if(argc > optind)
return usage(program_name);
if((t = fopen("/dev/tty", "rb")) == NULL){ if((t = fopen("/dev/tty", "rb")) == NULL){
fprintf(stderr, "%s: /dev/tty: %s\n", program_name, strerror(errno)); fprintf(stderr, "%s: /dev/tty: %s\n", program_name, strerror(errno));
return EX_SOFTWARE; return EX_SOFTWARE;
} }
r = 0;
for(;;){ for(;;){
if(r != RET_REQSHUTDOWN){ fputs(prompt, stderr);
fputs(prompt, stderr); fgets((char *)cmd, (sizeof cmd) / (sizeof *cmd), t);
fgets((char *)cmd, (sizeof cmd) / (sizeof *cmd), t); if(strchr((char *)cmd, '\n') == NULL){ /* fast-forward stream */
if(strchr((char *)cmd, '\n') == NULL){ /* fast-forward stream */ int c;
int c;
while((c = fgetc(t)) != '\n') while((c = fgetc(t)) != '\n')
if(c == EOF) if(c == EOF)
break; break;
}
} }
if(r == RET_REQSHUTDOWN || feof(t)){
fclose(t); if(feof(t))
return EX_OK; return EX_OK;
}
switch((r = cmdline_exec(cmd_map, (char *)cmd))){ switch(cmdline_exec(cmd_map, (char *)cmd)){
case RET_ARGSERROR: case RET_NOCMD: case RET_OK: case RET_USAGE: case RET_NOCMD: case RET_OK:
break; break;
case RET_REQSHUTDOWN: case RET_SOFTWARE: default: case RET_SOFTWARE:
r = RET_REQSHUTDOWN; return EX_SOFTWARE;
case RET_REQSHUTDOWN: default:
return EX_OK;
} }
} }