summaryrefslogtreecommitdiff
path: root/appl/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd')
-rw-r--r--appl/cmd/ar.b856
-rw-r--r--appl/cmd/cddb.b257
-rw-r--r--appl/cmd/lookman.b250
-rw-r--r--appl/cmd/man2html.b1328
-rw-r--r--appl/cmd/shutdown.b72
5 files changed, 2441 insertions, 322 deletions
diff --git a/appl/cmd/ar.b b/appl/cmd/ar.b
new file mode 100644
index 00000000..8ef237b8
--- /dev/null
+++ b/appl/cmd/ar.b
@@ -0,0 +1,856 @@
+implement Ar;
+
+#
+# ar - portable (ascii) format version
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "string.m";
+ str: String;
+
+Ar: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+ARMAG: con "!<arch>\n";
+SARMAG: con len ARMAG;
+ARFMAG0: con byte '`';
+ARFMAG1: con byte '\n';
+SARNAME: con 16; # ancient limit
+
+#
+# printable archive header
+# name[SARNAME] date[12] uid[6] gid[6] mode[8] size[10] fmag[2]
+#
+Oname: con 0;
+Lname: con SARNAME;
+Odate: con Oname+Lname;
+Ldate: con 12;
+Ouid: con Odate+Ldate;
+Luid: con 6;
+Ogid: con Ouid+Luid;
+Lgid: con 6;
+Omode: con Ogid+Lgid;
+Lmode: con 8;
+Osize: con Omode+Lmode;
+Lsize: con 10;
+Ofmag: con Osize+Lsize;
+Lfmag: con 2;
+SAR_HDR: con Ofmag+Lfmag; # 60
+
+#
+# The algorithm uses up to 3 temp files. The "pivot contents" is the
+# archive contents specified by an a, b, or i option. The temp files are
+# astart - contains existing contentss up to and including the pivot contents.
+# amiddle - contains new files moved or inserted behind the pivot.
+# aend - contains the existing contentss that follow the pivot contents.
+# When all contentss have been processed, function 'install' streams the
+# temp files, in order, back into the archive.
+#
+
+Armember: adt { # one per archive contents
+ name: string; # trimmed
+ length: int;
+ date: int;
+ uid: int;
+ gid: int;
+ mode: int;
+ size: int;
+ contents: array of byte;
+ fd: ref Sys->FD; # if contents is nil and fd is not nil, fd has contents
+ next: cyclic ref Armember;
+
+ new: fn(name: string, fd: ref Sys->FD): ref Armember;
+ rdhdr: fn(b: ref Iobuf): ref Armember;
+ read: fn(m: self ref Armember, b: ref Iobuf): int;
+ wrhdr: fn(m: self ref Armember, fd: ref Sys->FD);
+ write: fn(m: self ref Armember, fd: ref Sys->FD);
+ skip: fn(m: self ref Armember, b: ref Iobuf);
+ replace: fn(m: self ref Armember, name: string, fd: ref Sys->FD);
+ copyout: fn(m: self ref Armember, b: ref Iobuf, destfd: ref Sys->FD);
+};
+
+Arfile: adt { # one per tempfile
+ fd: ref Sys->FD; # paging file descriptor, nil if none allocated
+
+ head: ref Armember;
+ tail: ref Armember;
+
+ new: fn(): ref Arfile;
+ copy: fn(ar: self ref Arfile, b: ref Iobuf, mem: ref Armember);
+ insert: fn(ar: self ref Arfile, mem: ref Armember);
+ stream: fn(ar: self ref Arfile, fd: ref Sys->FD);
+ page: fn(ar: self ref Arfile): int;
+};
+
+File: adt {
+ name: string;
+ trimmed: string;
+ found: int;
+};
+
+man := "mrxtdpq";
+opt := "uvnbailo";
+
+aflag := 0;
+bflag := 0;
+cflag := 0;
+oflag := 0;
+uflag := 0;
+vflag := 0;
+
+pivotname: string;
+bout: ref Iobuf;
+stderr: ref Sys->FD;
+parts: array of ref Arfile;
+
+comfun: ref fn(a: string, f: array of ref File);
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ str = load String String->PATH;
+
+ stderr = sys->fildes(2);
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if(len args < 3)
+ usage();
+ args = tl args;
+ s := hd args; args = tl args;
+ for(i := 0; i < len s; i++){
+ case s[i] {
+ 'a' => aflag = 1;
+ 'b' => bflag = 1;
+ 'c' => cflag = 1;
+ 'd' => setcom(dcmd);
+ 'i' => bflag = 1;
+ 'l' => ; # ignored
+ 'm' => setcom(mcmd);
+ 'o' => oflag = 1;
+ 'p' => setcom(pcmd);
+ 'q' => setcom(qcmd);
+ 'r' => setcom(rcmd);
+ 't' => setcom(tcmd);
+ 'u' => uflag = 1;
+ 'v' => vflag = 1;
+ 'x' => setcom(xcmd);
+ * =>
+ sys->fprint(stderr, "ar: bad option `%c'\n", s[i]);
+ usage();
+ }
+ }
+ if(aflag && bflag){
+ sys->fprint(stderr, "ar: only one of 'a' and 'b' can be specified\n");
+ usage();
+ }
+ if(aflag || bflag){
+ pivotname = trim(hd args); args = tl args;
+ if(len args < 2)
+ usage();
+ }
+ if(comfun == nil){
+ if(uflag == 0){
+ sys->fprint(stderr, "ar: one of [%s] must be specified\n", man);
+ usage();
+ }
+ setcom(rcmd);
+ }
+ cp := hd args; args = tl args;
+ files := array[len args] of ref File;
+ for(i = 0; args != nil; args = tl args)
+ files[i++] = ref File(hd args, trim(hd args), 0);
+ comfun(cp, files); # do the command
+ for(i = 0; i < len files; i++)
+ if(!files[i].found){
+ sys->fprint(stderr, "ar: %s not found\n", files[i].name);
+ cp = "error";
+ }
+ bout.flush();
+ if(cp != nil)
+ raise "fail:"+cp;
+}
+
+#
+# select a command
+#
+setcom(fun: ref fn(s: string, f: array of ref File))
+{
+ if(comfun != nil){
+ sys->fprint(stderr, "ar: only one of [%s] allowed\n", man);
+ usage();
+ }
+ comfun = fun;
+}
+
+#
+# perform the 'r' and 'u' commands
+#
+rcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->ORDWR, 1);
+ parts = array[2] of {Arfile.new(), nil};
+ ap := parts[0];
+ if(bar != nil){
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if(bamatch(mem.name, pivotname)) # check for pivot
+ ap = parts[1] = Arfile.new();
+ f := match(files, mem.name);
+ if(f == nil){
+ ap.copy(bar, mem);
+ continue;
+ }
+ f.found = 1;
+ dfd := sys->open(f.name, Sys->OREAD);
+ if(dfd == nil){
+ if(len files > 0)
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
+ ap.copy(bar, mem);
+ continue;
+ }
+ if(uflag){
+ (ok, d) := sys->fstat(dfd);
+ if(ok < 0 || d.mtime <= mem.date){
+ if(ok < 0)
+ sys->fprint(stderr, "ar: cannot stat %s: %r\n", f.name);
+ ap.copy(bar, mem);
+ continue;
+ }
+ }
+ mem.skip(bar);
+ mesg('r', f.name);
+ mem.replace(f.name, dfd);
+ ap.insert(mem);
+ dfd = nil;
+ }
+ }
+ # copy in remaining files named on command line
+ for(i := 0; i < len files; i++){
+ f := files[i];
+ if(f.found)
+ continue;
+ f.found = 1;
+ dfd := sys->open(f.name, Sys->OREAD);
+ if(dfd != nil){
+ mesg('a', f.name);
+ parts[0].insert(Armember.new(f.trimmed, dfd));
+ }else
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
+ }
+ if(bar == nil && !cflag)
+ install(arname, parts, 1); # issue 'creating' msg
+ else
+ install(arname, parts, 0);
+}
+
+dcmd(arname: string, files: array of ref File)
+{
+ if(len files == 0)
+ return;
+ changed := 0;
+ parts = array[] of {Arfile.new()};
+ bar := openar(arname, Sys->ORDWR, 0);
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if(match(files, mem.name) != nil){
+ mesg('d', mem.name);
+ mem.skip(bar);
+ changed = 1;
+ }else
+ parts[0].copy(bar, mem);
+ mem = nil; # conserves memory
+ }
+ if(changed)
+ install(arname, parts, 0);
+}
+
+xcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->OREAD, 0);
+ i := 0;
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if((f := match(files, mem.name)) != nil){
+ f.found = 1;
+ fd := sys->create(f.name, Sys->OWRITE, mem.mode & 8r777);
+ if(fd == nil){
+ sys->fprint(stderr, "ar: cannot create %s: %r\n", f.name);
+ mem.skip(bar);
+ }else{
+ mesg('x', f.name);
+ mem.copyout(bar, fd);
+ if(oflag){
+ dx := sys->nulldir;
+ dx.atime = mem.date;
+ dx.mtime = mem.date;
+ if(sys->fwstat(fd, dx) < 0)
+ sys->fprint(stderr, "ar: can't set times on %s: %r", f.name);
+ }
+ fd = nil;
+ mem = nil;
+ }
+ if(len files > 0 && ++i >= len files)
+ break;
+ }else
+ mem.skip(bar);
+ }
+}
+
+pcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->OREAD, 0);
+ i := 0;
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if((f := match(files, mem.name)) != nil){
+ if(vflag)
+ sys->print("\n<%s>\n\n", f.name);
+ mem.copyout(bar, sys->fildes(1));
+ if(len files > 0 && ++i >= len files)
+ break;
+ }else
+ mem.skip(bar);
+ mem = nil; # we no longer need the contents
+ }
+}
+
+mcmd(arname: string, files: array of ref File)
+{
+ if(len files == 0)
+ return;
+ parts = array[3] of {Arfile.new(), Arfile.new(), nil};
+ bar := openar(arname, Sys->ORDWR, 0);
+ ap := parts[0];
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if(bamatch(mem.name, pivotname))
+ ap = parts[2] = Arfile.new();
+ if((f := match(files, mem.name)) != nil){
+ mesg('m', f.name);
+ parts[1].copy(bar, mem);
+ }else
+ ap.copy(bar, mem);
+ }
+ if(pivotname != nil && parts[2] == nil)
+ sys->fprint(stderr, "ar: %s not found - files moved to end\n", pivotname);
+ install(arname, parts, 0);
+}
+
+tcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->OREAD, 0);
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if((f := match(files, mem.name)) != nil){
+ longls := "";
+ if(vflag)
+ longls = longtext(mem)+" ";
+ bout.puts(longls+f.trimmed+"\n");
+ }
+ mem.skip(bar);
+ mem = nil;
+ }
+}
+
+qcmd(arname: string, files: array of ref File)
+{
+ if(aflag || bflag){
+ sys->fprint(stderr, "ar: abi not allowed with q\n");
+ raise "fail:usage";
+ }
+ fd := openrawar(arname, Sys->ORDWR, 1);
+ if(fd == nil){
+ if(!cflag)
+ sys->fprint(stderr, "ar: creating %s\n", arname);
+ fd = arcreate(arname);
+ }
+ # leave note group behind when writing archive; i.e. sidestep interrupts
+ sys->seek(fd, big 0, 2); # append
+ for(i := 0; i < len files; i++){
+ f := files[i];
+ f.found = 1;
+ dfd := sys->open(f.name, Sys->OREAD);
+ if(dfd != nil){
+ mesg('q', f.name);
+ mem := Armember.new(f.trimmed, dfd);
+ if(mem != nil){
+ mem.write(fd);
+ mem = nil;
+ }
+ }else
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
+ }
+}
+
+#
+# open an archive and validate its header
+#
+openrawar(arname: string, mode: int, errok: int): ref Sys->FD
+{
+ fd := sys->open(arname, mode);
+ if(fd == nil){
+ if(!errok){
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", arname);
+ raise "fail:error";
+ }
+ return nil;
+ }
+ mbuf := array[SARMAG] of byte;
+ if(sys->read(fd, mbuf, SARMAG) != SARMAG || string mbuf != ARMAG){
+ sys->fprint(stderr, "ar: %s not in archive format\n", arname);
+ raise "fail:error";
+ }
+ return fd;
+}
+
+openar(arname: string, mode: int, errok: int): ref Iobuf
+{
+ fd := openrawar(arname, mode, errok);
+ if(fd == nil)
+ return nil;
+ bfd := bufio->fopen(fd, mode);
+ bfd.seek(big SARMAG, 0);
+ return bfd;
+}
+
+#
+# create an archive and set its header
+#
+arcreate(arname: string): ref Sys->FD
+{
+ fd := sys->create(arname, Sys->OWRITE, 8r666);
+ if(fd == nil){
+ sys->fprint(stderr, "ar: cannot create %s: %r\n", arname);
+ raise "fail:create";
+ }
+ a := array of byte ARMAG;
+ mustwrite(fd, a, len a);
+ return fd;
+}
+
+#
+# error handling
+#
+wrerr()
+{
+ sys->fprint(stderr, "ar: write error: %r\n");
+ raise "fail:write error";
+}
+
+rderr()
+{
+ sys->fprint(stderr, "ar: read error: %r\n");
+ raise "fail:read error";
+}
+
+phaseerr(offset: big)
+{
+ sys->fprint(stderr, "ar: phase error at offset %bd\n", offset);
+ raise "fail:phase error";
+}
+
+usage()
+{
+ sys->fprint(stderr, "usage: ar [%s][%s] archive files ...\n", opt, man);
+ raise "fail:usage";
+}
+
+#
+# concatenate the several sequences of members into one archive
+#
+install(arname: string, seqs: array of ref Arfile, createflag: int)
+{
+ # leave process group behind when copying back; i.e. sidestep interrupts
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ if(createflag)
+ sys->fprint(stderr, "ar: creating %s\n", arname);
+ fd := arcreate(arname);
+ for(i := 0; i < len seqs; i++)
+ if((ap := seqs[i]) != nil)
+ ap.stream(fd);
+}
+
+#
+# return the command line File matching a given name
+#
+match(files: array of ref File, file: string): ref File
+{
+ if(len files == 0)
+ return ref File(file, file, 0); # empty list always matches
+ for(i := 0; i < len files; i++)
+ if(!files[i].found && files[i].trimmed == file){
+ files[i].found = 1;
+ return files[i];
+ }
+ return nil;
+}
+
+#
+# is `file' the pivot member's name and is the archive positioned
+# at the correct point wrt after or before options? return true if so.
+#
+state := 0;
+
+bamatch(file: string, pivot: string): int
+{
+ case state {
+ 0 => # looking for position file
+ if(aflag){
+ if(file == pivot)
+ state = 1;
+ }else if(bflag){
+ if(file == pivot){
+ state = 2; # found
+ return 1;
+ }
+ }
+ 1 => # found - after previous file
+ state = 2;
+ return 1;
+ 2 => # already found position file
+ ;
+ }
+ return 0;
+}
+
+#
+# output a message, if 'v' option was specified
+#
+mesg(c: int, file: string)
+{
+ if(vflag)
+ bout.puts(sys->sprint("%c - %s\n", c, file));
+}
+
+#
+# return just the file name
+#
+trim(s: string): string
+{
+ for(j := len s; j > 0 && s[j-1] == '/';)
+ j--;
+ k := 0;
+ for(i := 0; i < j; i++)
+ if(s[i] == '/')
+ k = i+1;
+ return s[k: j];
+}
+
+longtext(mem: ref Armember): string
+{
+ s := modes(mem.mode);
+ s += sys->sprint(" %3d/%1d", mem.uid, mem.gid);
+ s += sys->sprint(" %7ud", mem.size);
+ t := daytime->text(daytime->local(mem.date));
+ return s+sys->sprint(" %-12.12s %-4.4s ", t[4:], t[24:]);
+}
+
+mtab := array[] of {
+ "---", "--x", "-w-", "-wx",
+ "r--", "r-x", "rw-", "rwx"
+};
+
+modes(mode: int): string
+{
+ return mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7];
+}
+
+#
+# read the header for the next archive contents
+#
+Armember.rdhdr(b: ref Iobuf): ref Armember
+{
+ buf := array[SAR_HDR] of byte;
+ if((n := b.read(buf, len buf)) != len buf){
+ if(n == 0)
+ return nil;
+ if(n > 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ mem := ref Armember;
+ for(i := Oname+Lname; i > Oname; i--)
+ if(buf[i-1] != byte '/' && buf[i-1] != byte ' ')
+ break;
+ mem.name = string buf[Oname:i];
+ mem.date = intof(buf[Odate: Odate+Ldate], 10);
+ mem.uid = intof(buf[Ouid: Ouid+Luid], 10);
+ mem.gid = intof(buf[Ogid: Ogid+Lgid], 10);
+ mem.mode = intof(buf[Omode: Omode+Lmode], 8);
+ mem.size = intof(buf[Osize: Osize+Lsize], 10);
+ if(buf[Ofmag] != ARFMAG0 || buf[Ofmag+1] != ARFMAG1)
+ phaseerr(b.offset()-big SAR_HDR);
+ return mem;
+}
+
+intof(a: array of byte, base: int): int
+{
+ for(i := len a; i > 0; i--)
+ if(a[i-1] != byte ' '){
+ a = a[0:i];
+ break;
+ }
+ (n, s) := str->toint(string a, base);
+ if(s != nil){
+ sys->fprint(stderr, "ar: invalid integer in archive member's header: %q\n", string a);
+ raise "fail:error";
+ }
+ return n;
+}
+
+Armember.wrhdr(mem: self ref Armember, fd: ref Sys->FD)
+{
+ b := array[SAR_HDR] of {* => byte ' '};
+ nm := array of byte mem.name;
+ if(len nm > Lname)
+ nm = nm[0:Lname];
+ b[Oname:] = nm;
+ b[Odate:] = sys->aprint("%-12ud", mem.date);
+ b[Ouid:] = sys->aprint("%-6d", 0);
+ b[Ogid:] = sys->aprint("%-6d", 0);
+ b[Omode:] = sys->aprint("%-8uo", mem.mode);
+ b[Osize:] = sys->aprint("%-10ud", mem.size);
+ b[Ofmag] = ARFMAG0;
+ b[Ofmag+1] = ARFMAG1;
+ mustwrite(fd, b, len b);
+}
+
+#
+# make a new member from the given file, with the file's contents
+#
+Armember.new(name: string, fd: ref Sys->FD): ref Armember
+{
+ mem := ref Armember;
+ mem.replace(name, fd);
+ return mem;
+}
+
+#
+# replace the contents of an existing member
+#
+Armember.replace(mem: self ref Armember, name: string, fd: ref Sys->FD)
+{
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0){
+ sys->fprint(stderr, "ar: cannot stat %s: %r\n", name);
+ raise "fail:no stat";
+ }
+ mem.name = trim(name);
+ mem.date = d.mtime;
+ mem.uid = 0;
+ mem.gid = 0;
+ mem.mode = d.mode & 8r777;
+ mem.size = int d.length;
+ if(big mem.size != d.length){
+ sys->fprint(stderr, "ar: file %s too big\n", name);
+ raise "fail:error";
+ }
+ mem.fd = fd;
+ mem.contents = nil; # will be copied across from fd when needed
+}
+
+#
+# read the contents of an archive member
+#
+Armember.read(mem: self ref Armember, b: ref Iobuf): int
+{
+ if(mem.contents != nil)
+ return len mem.contents;
+ mem.contents = buffer(mem.size + (mem.size&1));
+ n := b.read(mem.contents, len mem.contents);
+ if(n != len mem.contents){
+ if(n >= 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ return n;
+}
+
+mustwrite(fd: ref Sys->FD, buf: array of byte, n: int)
+{
+ if(sys->write(fd, buf, n) != n)
+ wrerr();
+}
+
+#
+# write an archive member to ofd, including header
+#
+Armember.write(mem: self ref Armember, ofd: ref Sys->FD)
+{
+ mem.wrhdr(ofd);
+ if(mem.contents != nil){
+ mustwrite(ofd, mem.contents, len mem.contents);
+ return;
+ }
+ if(mem.fd == nil)
+ raise "ar: write nil fd";
+ buf := array[Sys->ATOMICIO] of byte; # could be bigger
+ for(nr := mem.size; nr > 0;){
+ n := nr;
+ if(n > len buf)
+ n = len buf;
+ n = sys->read(mem.fd, buf, n);
+ if(n <= 0){
+ if(n == 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ mustwrite(ofd, buf, n);
+ nr -= n;
+ }
+ if(mem.size & 1)
+ mustwrite(ofd, array[] of {byte '\n'}, 1);
+}
+
+#
+# seek past the current member's contents in b
+#
+Armember.skip(mem: self ref Armember, b: ref Iobuf)
+{
+ b.seek(big(mem.size + (mem.size&1)), 1);
+}
+
+#
+# copy a member's contents from memory or directly from an archive to another file
+#
+Armember.copyout(mem: self ref Armember, b: ref Iobuf, ofd: ref Sys->FD)
+{
+ if(mem.contents != nil){
+ mustwrite(ofd, mem.contents, len mem.contents);
+ return;
+ }
+ buf := array[Sys->ATOMICIO] of byte; # could be bigger
+ for(nr := mem.size; nr > 0;){
+ n := nr;
+ if(n > len buf)
+ n = len buf;
+ n = b.read(buf, n);
+ if(n <= 0){
+ if(n == 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ mustwrite(ofd, buf, n);
+ nr -= n;
+ }
+ if(mem.size & 1)
+ b.getc();
+}
+
+#
+# Temp file I/O subsystem. We attempt to cache all three temp files in
+# core. When we run out of memory we spill to disk.
+# The I/O model assumes that temp files:
+# 1) are only written on the end
+# 2) are only read from the beginning
+# 3) are only read after all writing is complete.
+# The architecture uses one control block per temp file. Each control
+# block anchors a chain of buffers, each containing an archive contents.
+#
+Arfile.new(): ref Arfile
+{
+ return ref Arfile;
+}
+
+#
+# copy the contents of mem at b into the temporary
+#
+Arfile.copy(ap: self ref Arfile, b: ref Iobuf, mem: ref Armember)
+{
+ mem.read(b);
+ ap.insert(mem);
+}
+
+#
+# insert a contents buffer into the contents chain
+#
+Arfile.insert(ap: self ref Arfile, mem: ref Armember)
+{
+ mem.next = nil;
+ if(ap.head == nil)
+ ap.head = mem;
+ else
+ ap.tail.next = mem;
+ ap.tail = mem;
+}
+
+#
+# stream the contents in a temp file to the file referenced by 'fd'.
+#
+Arfile.stream(ap: self ref Arfile, fd: ref Sys->FD)
+{
+ if(ap.fd != nil){ # copy prefix from disk
+ buf := array[Sys->ATOMICIO] of byte;
+ sys->seek(ap.fd, big 0, 0);
+ while((n := sys->read(ap.fd, buf, len buf)) > 0)
+ mustwrite(fd, buf, n);
+ if(n < 0)
+ rderr();
+ ap.fd = nil;
+ }
+ # dump the in-core buffers, which always follow the contents in the temp file
+ for(mem := ap.head; mem != nil; mem = mem.next)
+ mem.write(fd);
+}
+
+#
+# spill a member's contents to disk
+#
+
+totalmem := 0;
+warned := 0;
+tn := 0;
+
+Arfile.page(ap: self ref Arfile): int
+{
+ mem := ap.head;
+ if(ap.fd == nil && !warned){
+ pid := sys->pctl(0, nil);
+ for(i := 0;; i++){
+ name := sys->sprint("/tmp/art%d.%d.%d", pid, tn, i);
+ ap.fd = sys->create(name, Sys->OEXCL | Sys->ORDWR | Sys->ORCLOSE, 8r600);
+ if(ap.fd != nil)
+ break;
+ if(i >= 20){
+ warned =1;
+ sys->fprint(stderr,"ar: warning: can't create temp file %s: %r\n", name);
+ return 0; # we'll simply use the memory
+ }
+ }
+ tn++;
+ }
+ mem.write(ap.fd);
+ ap.head = mem.next;
+ if(ap.tail == mem)
+ ap.tail = mem.next;
+ totalmem -= len mem.contents;
+ return 1;
+}
+
+#
+# account for the space taken by a contents's contents,
+# pushing earlier contentss to disk to keep the space below a
+# reasonable level
+#
+
+buffer(n: int): array of byte
+{
+Flush:
+ while(totalmem + n > 1024*1024){
+ for(i := 0; i < len parts; i++)
+ if(parts[i] != nil && parts[i].page())
+ continue Flush;
+ break;
+ }
+ totalmem += n;
+ return array[n] of byte;
+}
diff --git a/appl/cmd/cddb.b b/appl/cmd/cddb.b
new file mode 100644
index 00000000..6265ba24
--- /dev/null
+++ b/appl/cmd/cddb.b
@@ -0,0 +1,257 @@
+implement Cddb;
+
+# this is a near transliteration of Plan 9 source, and subject to the Lucent Public License 1.02
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+
+Cddb: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+server := "freedb.freedb.org";
+debug := 0;
+tflag := 0;
+Tflag := 0;
+
+Track: adt {
+ n: int;
+ title: string;
+};
+
+Toc: adt {
+ diskid: int;
+ ntrack: int;
+ title: string;
+ track: array of Track;
+};
+
+DPRINT(fd: int, s: string)
+{
+ if(debug)
+ sys->fprint(sys->fildes(fd), "%s", s);
+}
+
+dumpcddb(t: ref Toc)
+{
+ sys->print("title %s\n", t.title);
+ for(i:=0; i<t.ntrack; i++){
+ if(tflag){
+ n := t.track[i+1].n;
+ if(i == t.ntrack-1)
+ n *= 75;
+ s := (n - t.track[i].n)/75;
+ sys->print("%d\t%s\t%d:%2.2d\n", i+1, t.track[i].title, s/60, s%60);
+ }
+ else
+ sys->print("%d\t%s\n", i+1, t.track[i].title);
+ }
+ if(Tflag){
+ s := t.track[i].n;
+ sys->print("Total time: %d:%2.2d\n", s/60, s%60);
+ }
+}
+
+cddbfilltoc(t: ref Toc): int
+{
+ (ok, conn) := sys->dial(netmkaddr(server, "tcp", "888"), nil);
+ if(ok < 0) {
+ sys->fprint(sys->fildes(2), "cddb: cannot dial %s: %r\n", server);
+ return -1;
+ }
+ bin := bufio->fopen(conn.dfd, Bufio->OREAD);
+
+ if((p:=getline(bin)) == nil || atoi(p)/100 != 2)
+ return died(p);
+
+ sys->fprint(conn.dfd, "cddb hello gre plan9 9cd 1.0\r\n");
+ if((p = getline(bin)) == nil || atoi(p)/100 != 2)
+ return died(p);
+
+ #
+ # Protocol level 6 is the same as level 5 except that
+ # the character set is now UTF-8 instead of ISO-8859-1.
+ #
+ sys->fprint(conn.dfd, "proto 6\r\n");
+ if((p = getline(bin)) == nil || atoi(p)/100 != 2)
+ return died(p);
+ DPRINT(2, sys->sprint("%s\n", p));
+
+ sys->fprint(conn.dfd, "cddb query %8.8ux %d", t.diskid, t.ntrack);
+ DPRINT(2, sys->sprint("cddb query %8.8ux %d", t.diskid, t.ntrack));
+ for(i:=0; i<t.ntrack; i++) {
+ sys->fprint(conn.dfd, " %d", t.track[i].n);
+ DPRINT(2, sys->sprint(" %d", t.track[i].n));
+ }
+ sys->fprint(conn.dfd, " %d\r\n", t.track[t.ntrack].n);
+ DPRINT(2, sys->sprint(" %d\r\n", t.track[t.ntrack].n));
+
+ if((p = getline(bin)) == nil || atoi(p)/100 != 2)
+ return died(p);
+ DPRINT(2, sys->sprint("cddb: %s\n", p));
+ (nf, fl) := sys->tokenize(p, " \t\n\r");
+ if(nf < 1)
+ return died(p);
+
+ categ, id: string;
+ case atoi(hd fl) {
+ 200 => # exact match
+ if(nf < 3)
+ return died(p);
+ categ = hd tl fl;
+ id = hd tl tl fl;
+ 211 => # close matches
+ if((p = getline(bin)) == nil)
+ return died(nil);
+ if(p[0] == '.') # no close matches?
+ return died(nil);
+
+ # accept first match
+ (nsf, f) := sys->tokenize(p, " \t\n\r");
+ if(nsf < 2)
+ return died(p);
+ categ = hd f;
+ id = hd tl f;
+
+ # snarf rest of buffer
+ while(p[0] != '.') {
+ if((p = getline(bin)) == nil)
+ return died(p);
+ DPRINT(2, sys->sprint("cddb: %s\n", p));
+ }
+ 202 or # no match
+ * =>
+ return died(p);
+ }
+
+ t.title = "";
+ for(i=0; i<t.ntrack; i++)
+ t.track[i].title = "";
+
+ # fetch results for this cd
+ sys->fprint(conn.dfd, "cddb read %s %s\r\n", categ, id);
+ do {
+ if((p = getline(bin)) == nil)
+ return died(nil);
+DPRINT(2, sys->sprint("cddb %s\n", p));
+ if(len p >= 7 && p[0:7] == "DTITLE=")
+ t.title += p[7:];
+ else if(len p >= 6 && p[0:6] == "TTITLE"&& isdigit(p[6])) {
+ i = atoi(p[6:]);
+ if(i < t.ntrack) {
+ p = p[6:];
+ while(p != nil && isdigit(p[0]))
+ p = p[1:];
+ if(p != nil && p[0] == '=')
+ p = p[1:];
+ t.track[i].title += p;
+ }
+ }
+ } while(p[0] != '.');
+
+ sys->fprint(conn.dfd, "quit\r\n");
+
+ return 0;
+}
+
+getline(f: ref Iobuf): string
+{
+ p := f.gets('\n');
+ while(p != nil && isspace(p[len p-1]))
+ p = p[0: len p-1];
+ return p;
+}
+
+isdigit(c: int): int
+{
+ return c>='0' && c <= '9';
+}
+
+isspace(c: int): int
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+died(p: string): int
+{
+ sys->fprint(sys->fildes(2), "cddb: error talking to server\n");
+ if(p != nil){
+ p = p[0:len p-1];
+ sys->fprint(sys->fildes(2), "cddb: server says: %s\n", p);
+ }
+ return -1;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ str = load String String->PATH;
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("cddb [-DTt] [-s server] query diskid n ...");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'D' => debug = 1;
+ 's' => server = arg->earg();
+ 'T' => Tflag = 1; tflag = 1;
+ 't' => tflag = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ argc := len args;
+ if(argc < 3 || hd args != "query")
+ arg->usage();
+ arg = nil;
+
+ ntrack := atoi(hd tl tl args);
+ toc := ref Toc(str->toint(hd tl args, 16).t0, ntrack, nil, array[ntrack+1] of Track);
+ if(argc != 3+toc.ntrack+1){
+ sys->fprint(sys->fildes(2), "cddb: argument count does not match given ntrack");
+ raise "fail:error";
+ }
+ args = tl tl tl args;
+
+ for(i:=0; i<=toc.ntrack; i++){ # <=?
+ toc.track[i].n = atoi(hd args);
+ args = tl args;
+ }
+
+ if(cddbfilltoc(toc) < 0)
+ raise "fail:whoops";
+
+ dumpcddb(toc);
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, nil) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
+
+atoi(s: string): int
+{
+ return int s;
+}
diff --git a/appl/cmd/lookman.b b/appl/cmd/lookman.b
deleted file mode 100644
index 53557c8b..00000000
--- a/appl/cmd/lookman.b
+++ /dev/null
@@ -1,250 +0,0 @@
-implement Lookman;
-include "sys.m";
-include "bufio.m";
-include "draw.m";
-
-
-Lookman : module {
- init : fn (ctxt : ref Draw->Context, argv : list of string);
-};
-
-sys : Sys;
-bufio : Bufio;
-Iobuf : import bufio;
-
-ctype := array [256] of { * => byte 0 };
-
-MANINDEX : con "/man/index";
-
-init(nil : ref Draw->Context, argv : list of string)
-{
- sys = load Sys Sys->PATH;
- bufio = load Bufio Bufio->PATH;
-
- if (bufio == nil)
- raise "init:fail";
-
- # setup our char conversion table
- # map upper-case to lower-case
- for (i := 'A'; i <= 'Z'; i++)
- ctype[i] = byte ((i - 'A') + 'a');
-
- # only allow the following chars
- okchars := "abcdefghijklmnopqrstuvwxyz0123456789+.:½ ";
- for (i = 0; i < len okchars; i++) {
- ch := okchars[i];
- ctype[ch] = byte ch;
- }
-
- stdout := bufio->fopen(sys->fildes(1), Sys->OWRITE);
-
- argv = tl argv;
- paths := lookup(argv);
- for (; paths != nil; paths = tl paths)
- stdout.puts(sys->sprint("%s\n", hd paths));
- stdout.flush();
-}
-
-lookup(words : list of string) : list of string
-{
- # open the index file
- manindex := bufio->open(MANINDEX, Sys->OREAD);
- if (manindex == nil) {
- sys->print("cannot open %s: %r\n", MANINDEX);
- return nil;
- }
-
- # convert to lower-case and discard funny chars
- keywords : list of string;
- for (; words != nil; words = tl words) {
- word := hd words;
- kw := "";
- for (i := 0; i < len word; i++) {
- ch := word[i];
- if (ch < len ctype && ctype[ch] != byte 0)
- kw[len kw] = int ctype[ch];
- }
- if (kw != "")
- keywords = kw :: keywords;
- }
-
- if (keywords == nil)
- return nil;
-
- keywords = sortuniq(keywords);
- matches : list of list of string;
-
- for (; keywords != nil; keywords = tl keywords) {
- kw := hd keywords;
- matchlist := look(manindex, '\t', kw);
- pathlist : list of string = nil;
- for (; matchlist != nil; matchlist = tl matchlist) {
- line := hd matchlist;
- (n, toks) := sys->tokenize(line, "\t");
- if (n != 2)
- continue;
- pathlist = hd tl toks :: pathlist;
- }
- if (pathlist != nil)
- matches = pathlist :: matches;
- }
-
- return intersect(matches);
-}
-
-getentry(iob : ref Iobuf) : (string, string)
-{
- while ((s := iob.gets('\n')) != nil) {
- if (s[len s -1] == '\n')
- s = s[0:len s -1];
- if (s == nil)
- continue;
- (n, toks) := sys->tokenize(s, "\t");
- if (n != 2)
- continue;
- return (hd toks, hd tl toks);
- }
- return (nil, nil);
-}
-
-sortuniq(strlist : list of string) : list of string
-{
- strs := array [len strlist] of string;
- for (i := 0; strlist != nil; (i, strlist) = (i+1, tl strlist))
- strs[i] = hd strlist;
-
- # simple sort (greatest first)
- for (i = 0; i < len strs - 1; i++) {
- for (j := i+1; j < len strs; j++)
- if (strs[i] < strs[j])
- (strs[i], strs[j]) = (strs[j], strs[i]);
- }
-
- # construct list (result is ascending)
- r : list of string;
- prev := "";
- for (i = 0; i < len strs; i++) {
- if (strs[i] != prev) {
- r = strs[i] :: r;
- prev = strs[i];
- }
- }
- return r;
-}
-
-intersect(strlists : list of list of string) : list of string
-{
- if (strlists == nil)
- return nil;
-
- okl := hd strlists;
- for (strlists = tl strlists; okl != nil && strlists != nil; strlists = tl strlists) {
- find := hd strlists;
- found : list of string = nil;
- for (; okl != nil; okl = tl okl) {
- ok := hd okl;
- for (scanl := find; scanl != nil; scanl = tl scanl) {
- scan := hd scanl;
- if (scan == ok) {
- found = ok :: found;
- break;
- }
- }
- }
- okl = found;
- }
- return sortuniq(okl);
-}
-
-# binary search for key in f.
-# based on Plan 9 look.c
-#
-look(f: ref Iobuf, sep: int, key: string): list of string
-{
- bot := mid := 0;
- top := int f.seek(big 0, Sys->SEEKEND);
- key = canon(key, sep);
-
- for (;;) {
- mid = (top + bot) / 2;
- f.seek(big mid, Sys->SEEKSTART);
- c: int;
- do {
- c = f.getb();
- mid++;
- } while (c != Bufio->EOF && c != Bufio->ERROR && c != '\n');
- (entry, eof) := getword(f);
- if (entry == nil && eof)
- break;
- entry = canon(entry, sep);
- case comparewords(key, entry) {
- -2 or -1 or 0 =>
- if (top <= mid)
- break;
- top = mid;
- continue;
- 1 or 2 =>
- bot = mid;
- continue;
- }
- break;
- }
- matchlist : list of string;
- f.seek(big bot, Sys->SEEKSTART);
- for (;;) {
- (entry, eof) := getword(f);
- if (entry == nil && eof)
- return matchlist;
- word := canon(entry, sep);
- case comparewords(key, word) {
- -1 or 0 =>
- matchlist = entry :: matchlist;
- continue;
- 1 or 2 =>
- continue;
- }
- break;
- }
- return matchlist;
-}
-
-comparewords(s, t: string): int
-{
- if (s == t)
- return 0;
- i := 0;
- for (; i < len s && i < len t && s[i] == t[i]; i++)
- ;
- if (i >= len s)
- return -1;
- if (i >= len t)
- return 1;
- if (s[i] < t[i])
- return -2;
- return 2;
-}
-
-getword(f: ref Iobuf): (string, int)
-{
- ret := "";
- for (;;) {
- c := f.getc();
- if (c == Bufio->EOF || c == Bufio->ERROR)
- return (ret, 0);
- if (c == '\n')
- break;
- ret[len ret] = c;
- }
- return (ret, 1);
-}
-
-canon(s: string, sep: int): string
-{
- if (sep < 0)
- return s;
- i := 0;
- for (; i < len s; i++)
- if (s[i] == sep)
- break;
- return s[0:i];
-}
diff --git a/appl/cmd/man2html.b b/appl/cmd/man2html.b
new file mode 100644
index 00000000..9eda5940
--- /dev/null
+++ b/appl/cmd/man2html.b
@@ -0,0 +1,1328 @@
+implement Man2html;
+
+include "sys.m";
+ stderr: ref Sys->FD;
+ sys: Sys;
+ print, fprint, sprint: import sys;
+
+
+include "bufio.m";
+
+include "draw.m";
+
+include "daytime.m";
+ dt: Daytime;
+
+include "string.m";
+ str: String;
+
+Man2html: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+Runeself: con 16r80;
+false, true: con iota;
+
+Troffspec: adt {
+ name: string;
+ value: string;
+};
+
+tspec := array [] of { Troffspec
+ ("ff", "ff"),
+ ("fi", "fi"),
+ ("fl", "fl"),
+ ("Fi", "ffi"),
+ ("ru", "_"),
+ ("em", "&#173;"),
+ ("14", "&#188;"),
+ ("12", "&#189;"),
+ ("co", "&#169;"),
+ ("de", "&#176;"),
+ ("dg", "&#161;"),
+ ("fm", "&#180;"),
+ ("rg", "&#174;"),
+# ("bu", "*"),
+ ("bu", "•"),
+ ("sq", "&#164;"),
+ ("hy", "-"),
+ ("pl", "+"),
+ ("mi", "-"),
+ ("mu", "&#215;"),
+ ("di", "&#247;"),
+ ("eq", "="),
+ ("==", "=="),
+ (">=", ">="),
+ ("<=", "<="),
+ ("!=", "!="),
+ ("+-", "&#177;"),
+ ("no", "&#172;"),
+ ("sl", "/"),
+ ("ap", "&"),
+ ("~=", "~="),
+ ("pt", "oc"),
+ ("gr", "GRAD"),
+ ("->", "->"),
+ ("<-", "<-"),
+ ("ua", "^"),
+ ("da", "v"),
+ ("is", "Integral"),
+ ("pd", "DIV"),
+ ("if", "oo"),
+ ("sr", "-/"),
+ ("sb", "(~"),
+ ("sp", "~)"),
+ ("cu", "U"),
+ ("ca", "(^)"),
+ ("ib", "(="),
+ ("ip", "=)"),
+ ("mo", "C"),
+ ("es", "&Oslash;"),
+ ("aa", "&#180;"),
+ ("ga", "`"),
+ ("ci", "O"),
+ ("L1", "Lucent"),
+ ("sc", "&#167;"),
+ ("dd", "++"),
+ ("lh", "<="),
+ ("rh", "=>"),
+ ("lt", "("),
+ ("rt", ")"),
+ ("lc", "|"),
+ ("rc", "|"),
+ ("lb", "("),
+ ("rb", ")"),
+ ("lf", "|"),
+ ("rf", "|"),
+ ("lk", "|"),
+ ("rk", "|"),
+ ("bv", "|"),
+ ("ts", "s"),
+ ("br", "|"),
+ ("or", "|"),
+ ("ul", "_"),
+ ("rn", " "),
+ ("*p", "PI"),
+ ("**", "*"),
+};
+
+ Entity: adt {
+ name: string;
+ value: int;
+ };
+ Entities: array of Entity;
+
+Entities = array[] of {
+ Entity( "&#161;", '¡' ),
+ Entity( "&#162;", '¢' ),
+ Entity( "&#163;", '£' ),
+ Entity( "&#164;", '¤' ),
+ Entity( "&#165;", '¥' ),
+ Entity( "&#166;", '¦' ),
+ Entity( "&#167;", '§' ),
+ Entity( "&#168;", '¨' ),
+ Entity( "&#169;", '©' ),
+ Entity( "&#170;", 'ª' ),
+ Entity( "&#171;", '«' ),
+ Entity( "&#172;", '¬' ),
+ Entity( "&#173;", '­' ),
+ Entity( "&#174;", '®' ),
+ Entity( "&#175;", '¯' ),
+ Entity( "&#176;", '°' ),
+ Entity( "&#177;", '±' ),
+ Entity( "&#178;", '²' ),
+ Entity( "&#179;", '³' ),
+ Entity( "&#180;", '´' ),
+ Entity( "&#181;", 'µ' ),
+ Entity( "&#182;", '¶' ),
+ Entity( "&#183;", '·' ),
+ Entity( "&#184;", '¸' ),
+ Entity( "&#185;", '¹' ),
+ Entity( "&#186;", 'º' ),
+ Entity( "&#187;", '»' ),
+ Entity( "&#188;", '¼' ),
+ Entity( "&#189;", '½' ),
+ Entity( "&#190;", '¾' ),
+ Entity( "&#191;", '¿' ),
+ Entity( "&Agrave;", 'À' ),
+ Entity( "&Aacute;", 'Á' ),
+ Entity( "&Acirc;", 'Â' ),
+ Entity( "&Atilde;", 'Ã' ),
+ Entity( "&Auml;", 'Ä' ),
+ Entity( "&Aring;", 'Å' ),
+ Entity( "&AElig;", 'Æ' ),
+ Entity( "&Ccedil;", 'Ç' ),
+ Entity( "&Egrave;", 'È' ),
+ Entity( "&Eacute;", 'É' ),
+ Entity( "&Ecirc;", 'Ê' ),
+ Entity( "&Euml;", 'Ë' ),
+ Entity( "&Igrave;", 'Ì' ),
+ Entity( "&Iacute;", 'Í' ),
+ Entity( "&Icirc;", 'Î' ),
+ Entity( "&Iuml;", 'Ï' ),
+ Entity( "&ETH;", 'Ð' ),
+ Entity( "&Ntilde;", 'Ñ' ),
+ Entity( "&Ograve;", 'Ò' ),
+ Entity( "&Oacute;", 'Ó' ),
+ Entity( "&Ocirc;", 'Ô' ),
+ Entity( "&Otilde;", 'Õ' ),
+ Entity( "&Ouml;", 'Ö' ),
+ Entity( "&215;", '×' ),
+ Entity( "&Oslash;", 'Ø' ),
+ Entity( "&Ugrave;", 'Ù' ),
+ Entity( "&Uacute;", 'Ú' ),
+ Entity( "&Ucirc;", 'Û' ),
+ Entity( "&Uuml;", 'Ü' ),
+ Entity( "&Yacute;", 'Ý' ),
+ Entity( "&THORN;", 'Þ' ),
+ Entity( "&szlig;", 'ß' ),
+ Entity( "&agrave;", 'à' ),
+ Entity( "&aacute;", 'á' ),
+ Entity( "&acirc;", 'â' ),
+ Entity( "&atilde;", 'ã' ),
+ Entity( "&auml;", 'ä' ),
+ Entity( "&aring;", 'å' ),
+ Entity( "&aelig;", 'æ' ),
+ Entity( "&ccedil;", 'ç' ),
+ Entity( "&egrave;", 'è' ),
+ Entity( "&eacute;", 'é' ),
+ Entity( "&ecirc;", 'ê' ),
+ Entity( "&euml;", 'ë' ),
+ Entity( "&igrave;", 'ì' ),
+ Entity( "&iacute;", 'í' ),
+ Entity( "&icirc;", 'î' ),
+ Entity( "&iuml;", 'ï' ),
+ Entity( "&eth;", 'ð' ),
+ Entity( "&ntilde;", 'ñ' ),
+ Entity( "&ograve;", 'ò' ),
+ Entity( "&oacute;", 'ó' ),
+ Entity( "&ocirc;", 'ô' ),
+ Entity( "&otilde;", 'õ' ),
+ Entity( "&ouml;", 'ö' ),
+ Entity( "&247;", '÷' ),
+ Entity( "&oslash;", 'ø' ),
+ Entity( "&ugrave;", 'ù' ),
+ Entity( "&uacute;", 'ú' ),
+ Entity( "&ucirc;", 'û' ),
+ Entity( "&uuml;", 'ü' ),
+ Entity( "&yacute;", 'ý' ),
+ Entity( "&thorn;", 'þ' ),
+ Entity( "&yuml;", 'ÿ' ), # &#255;
+
+ Entity( "&#SPACE;", ' ' ),
+ Entity( "&#RS;", '\n' ),
+ Entity( "&#RE;", '\r' ),
+ Entity( "&quot;", '"' ),
+ Entity( "&amp;", '&' ),
+ Entity( "&lt;", '<' ),
+ Entity( "&gt;", '>' ),
+
+ Entity( "CAP-DELTA", 'Δ' ),
+ Entity( "ALPHA", 'α' ),
+ Entity( "BETA", 'β' ),
+ Entity( "DELTA", 'δ' ),
+ Entity( "EPSILON", 'ε' ),
+ Entity( "THETA", 'θ' ),
+ Entity( "MU", 'μ' ),
+ Entity( "PI", 'π' ),
+ Entity( "TAU", 'τ' ),
+ Entity( "CHI", 'χ' ),
+
+ Entity( "<-", '←' ),
+ Entity( "^", '↑' ),
+ Entity( "->", '→' ),
+ Entity( "v", '↓' ),
+ Entity( "!=", '≠' ),
+ Entity( "<=", '≤' ),
+ Entity( nil, 0 ),
+};
+
+
+Hit: adt {
+ glob: string;
+ chap: string;
+ mtype: string;
+ page: string;
+};
+
+Lnone, Lordered, Lunordered, Ldef, Lother: con iota; # list types
+
+Chaps: adt {
+ name: string;
+ primary: int;
+};
+
+Types: adt {
+ name: string;
+ desc: string;
+};
+
+
+# having two separate flags here allows for inclusion of old-style formatted pages
+# under a new-style three-level tree
+Oldstyle: adt {
+ names: int; # two-level directory tree?
+ fmt: int; # old internal formats: e.g., "B" font means "L"; name in .TH in all caps
+};
+
+Href: adt {
+ title: string;
+ chap: string;
+ mtype: string;
+ man: string;
+};
+
+# per-thread global data
+Global: adt {
+ bufio: Bufio;
+ bin: ref Bufio->Iobuf;
+ bout: ref Bufio->Iobuf;
+ topname: string; # name of the top level categories in the manual
+ chaps: array of Chaps; # names of top-level partitions of this manual
+ types: array of Types; # names of second-level partitions
+ oldstyle: Oldstyle;
+ mantitle: string;
+ mandir: string;
+ thisone: Hit; # man page we're displaying
+ mtime: int; # last modification time of thisone
+ href: Href; # hrefs of components of this man page
+ hits: array of Hit;
+ nhits: int;
+ list_type: int;
+ pm: string; # proprietary marking
+ def_goobie: string; # deferred goobie
+ sop: int; # output at start of paragraph?
+ sol: int; # input at start of line?
+ broken: int; # output at a break?
+ fill: int; # in fill mode?
+ pre: int; # in PRE block?
+ example: int; # an example active?
+ ipd: int; # emit inter-paragraph distance?
+ indents: int;
+ hangingdt: int;
+ curfont: string; # current font
+ prevfont: string; # previous font
+ lastc: int; # previous char from input scanner
+ def_sm: int; # amount of deferred "make smaller" request
+
+ mk_href_chap: fn(g: self ref Global, chap: string);
+ mk_href_man: fn(g: self ref Global, man: string, oldstyle: int);
+ mk_href_mtype: fn(g: self ref Global, chap, mtype: string);
+ dobreak: fn(g: self ref Global);
+ print: fn(g: self ref Global, s: string);
+ softbr: fn(g: self ref Global): string;
+ softp: fn(g: self ref Global): string;
+};
+
+
+usage()
+{
+ sys->fprint(stderr, "Usage: man2html file [section]\n");
+ raise "fail:usage";
+}
+
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ str = load String String->PATH;
+ dt = load Daytime Daytime->PATH;
+ g := Global_init();
+ if(args != nil)
+ args = tl args;
+ if(args == nil)
+ usage();
+ page := hd args;
+ args = tl args;
+ section := "1";
+ if(args != nil)
+ section = hd args;
+ hit := Hit ("", "man", section, page);
+ domanpage(g, hit);
+ g.bufio->g.bout.flush();
+}
+
+# remove markup from a string
+# doesn't handle nested/quoted delimiters
+demark(s: string): string
+{
+ t: string;
+ clean := true;
+ for (i := 0; i < len s; i++) {
+ case s[i] {
+ '<' =>
+ clean = false;
+ '>' =>
+ clean = true;
+ * =>
+ if (clean)
+ t[len t] = s[i];
+ }
+ }
+ return t;
+}
+
+
+#
+# Convert an individual man page to HTML and output.
+#
+domanpage(g: ref Global, man: Hit)
+{
+ file := man.page;
+ g.bin = g.bufio->open(file, Bufio->OREAD);
+ g.bout = g.bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ if (g.bin == nil) {
+ fprint(stderr, "Cannot open %s: %r\n", file);
+ return;
+ }
+ (err, info) := sys->fstat(g.bin.fd);
+ if (! err) {
+ g.mtime = info.mtime;
+ }
+ g.thisone = man;
+ while ((p := getnext(g)) != nil) {
+ c := p[0];
+ if (c == '.' && g.sol) {
+ if (g.pre) {
+ g.print("</PRE>");
+ g.pre = false;
+ }
+ dogoobie(g, false);
+ dohangingdt(g);
+ } else if (g.def_goobie != nil || g.def_sm != 0) {
+ g.bufio->g.bin.ungetc();
+ dogoobie(g, true);
+ } else if (c == '\n') {
+ g.print(p);
+ dohangingdt(g);
+ } else
+ g.print(p);
+ }
+ if (g.pm != nil) {
+ g.print("<BR><BR><BR><FONT SIZE=-2><CENTER>\n");
+ g.print(g.pm);
+ g.print("<BR></CENTER></FONT>\n");
+ }
+ closeall(g, 0);
+ rev(g, g.bin);
+}
+
+dogoobie(g: ref Global, deferred: int)
+{
+ # read line, translate special chars
+ line: string;
+ while ((token := getnext(g)) != "\n") {
+ if (token == nil)
+ return;
+ line += token;
+ }
+
+ # parse into arguments
+ argl, rargl: list of string; # create reversed version, then invert
+ while ((line = str->drop(line, " \t")) != nil)
+ if (line[0] == '"') {
+ (token, line) = split(line[1:], '"');
+ rargl = token :: rargl;
+ } else {
+ (token, line) = str->splitl(line, " \t");
+ rargl = token :: rargl;
+ }
+
+ if (rargl == nil && !deferred)
+ return;
+ for ( ; rargl != nil; rargl = tl rargl)
+ argl = hd rargl :: argl;
+
+ def_sm := g.def_sm;
+ if (deferred && def_sm > 0) {
+ g.print(sprint("<FONT SIZE=-%d>", def_sm));
+ if (g.def_goobie == nil)
+ argl = "dS" :: argl; # dS is our own local creation
+ }
+
+ subgoobie(g, argl);
+
+ if (deferred && def_sm > 0) {
+ g.def_sm = 0;
+ g.print("</FONT>");
+ }
+}
+
+subgoobie(g: ref Global, argl: list of string)
+{
+ if (g.def_goobie != nil) {
+ argl = g.def_goobie :: argl;
+ g.def_goobie = nil;
+ if (tl argl == nil)
+ return;
+ }
+
+ # the command part is at most two characters, but may be concatenated with the first arg
+ cmd := hd argl;
+ argl = tl argl;
+ if (len cmd > 2) {
+ cmd = cmd[0:2];
+ argl = cmd[2:] :: argl;
+ }
+
+ case cmd {
+
+ "B" or "I" or "L" or "R" =>
+ font(g, cmd, argl); # "R" macro implicitly generated by deferred R* macros
+
+ "BI" or "BL" or "BR" or
+ "IB" or "IL" or
+ "LB" or "LI" or
+ "RB" or "RI" or "RL" =>
+ altfont(g, cmd[0:1], cmd[1:2], argl, true);
+
+ "IR" or "LR" =>
+ anchor(g, cmd[0:1], cmd[1:2], argl); # includes man page refs ("IR" is old style, "LR" is new)
+
+ "dS" =>
+ printargs(g, argl);
+ g.print("\n");
+
+ "1C" or "2C" or "DT" or "TF" => # ignore these
+ return;
+
+ "P" or "PP" or "LP" =>
+ g_PP(g);
+
+ "EE" => g_EE(g);
+ "EX" => g_EX(g);
+ "HP" => g_HP_TP(g, 1);
+ "IP" => g_IP(g, argl);
+ "PD" => g_PD(g, argl);
+ "PM" => g_PM(g, argl);
+ "RE" => g_RE(g);
+ "RS" => g_RS(g);
+ "SH" => g_SH(g, argl);
+ "SM" => g_SM(g, argl);
+ "SS" => g_SS(g, argl);
+ "TH" => g_TH(g, argl);
+ "TP" => g_HP_TP(g, 3);
+
+ "br" => g_br(g);
+ "sp" => g_sp(g, argl);
+ "ti" => g_br(g);
+ "nf" => g_nf(g);
+ "fi" => g_fi(g);
+ "ft" => g_ft(g, argl);
+
+ * => return; # ignore unrecognized commands
+ }
+
+}
+
+g_br(g: ref Global)
+{
+ if (g.hangingdt != 0) {
+ g.print("<DD>");
+ g.hangingdt = 0;
+ } else if (g.fill && ! g.broken)
+ g.print("<BR>\n");
+ g.broken = true;
+}
+
+g_EE(g: ref Global)
+{
+ g.print("</PRE>\n");
+ g.fill = true;
+ g.broken = true;
+ g.example = false;
+}
+
+g_EX(g: ref Global)
+{
+ g.print("<PRE>");
+ if (! g.broken)
+ g.print("\n");
+ g.sop = true;
+ g.fill = false;
+ g.broken = true;
+ g.example = true;
+}
+
+g_fi(g: ref Global)
+{
+ if (g.fill)
+ return;
+ g.fill = true;
+ g.print("<P style=\"display: inline; white-space: normal\">\n");
+ g.broken = true;
+ g.sop = true;
+}
+
+g_ft(g: ref Global, argl: list of string)
+{
+ font: string;
+ arg: string;
+
+ if (argl == nil)
+ arg = "P";
+ else
+ arg = hd argl;
+
+ if (g.curfont != nil)
+ g.print(sprint("</%s>", g.curfont));
+
+ case arg {
+ "2" or "I" =>
+ font = "I";
+ "3" or "B" =>
+ font = "B";
+ "5" or "L" =>
+ font = "TT";
+ "P" =>
+ font = g.prevfont;
+ * =>
+ font = nil;
+ }
+ g.prevfont = g.curfont;
+ g.curfont = font;
+ if (g.curfont != nil)
+ if (g.fill)
+ g.print(sprint("<%s>", g.curfont));
+ else
+ g.print(sprint("<%s style=\"white-space: pre\">", g.curfont));
+}
+
+# level == 1 is a .HP; level == 3 is a .TP
+g_HP_TP(g: ref Global, level: int)
+{
+ case g.list_type {
+ Ldef =>
+ if (g.hangingdt != 0)
+ g.print("<DD>");
+ g.print(g.softbr() + "<DT>");
+ * =>
+ closel(g);
+ g.list_type = Ldef;
+ g.print("<DL compact>\n" + g.softbr() + "<DT>");
+ }
+ g.hangingdt = level;
+ g.broken = true;
+}
+
+g_IP(g: ref Global, argl: list of string)
+{
+ case g.list_type {
+
+ Lordered or Lunordered or Lother =>
+ ; # continue with an existing list
+
+ * =>
+ # figure out the type of a new list and start it
+ closel(g);
+ arg := "";
+ if (argl != nil)
+ arg = hd argl;
+ case arg {
+ "1" or "i" or "I" or "a" or "A" =>
+ g.list_type = Lordered;
+ g.print(sprint("<OL type=%s>\n", arg));
+ "*" or "•" or "&#8226;" =>
+ g.list_type = Lunordered;
+ g.print("<UL type=disc>\n");
+ "○" or "&#9675;"=>
+ g.list_type = Lunordered;
+ g.print("<UL type=circle>\n");
+ "□" or "&#9633;" =>
+ g.list_type = Lunordered;
+ g.print("<UL type=square>\n");
+ * =>
+ g.list_type = Lother;
+ g.print("<DL compact>\n");
+ }
+ }
+
+ # actually do this list item
+ case g.list_type {
+ Lother =>
+ g.print(g.softp()); # make sure there's space before each list item
+ if (argl != nil) {
+ g.print("<DT>");
+ printargs(g, argl);
+ }
+ g.print("\n<DD>");
+
+ Lordered or Lunordered =>
+ g.print(g.softp() + "<LI>");
+ }
+ g.broken = true;
+}
+
+g_nf(g: ref Global)
+{
+ if (! g.fill)
+ return;
+ g.fill = false;
+ g.print("<PRE>\n");
+ g.broken = true;
+ g.sop = true;
+ g.pre = true;
+}
+
+g_PD(g: ref Global, argl: list of string)
+{
+ if (len argl == 1 && hd argl == "0")
+ g.ipd = false;
+ else
+ g.ipd = true;
+}
+
+g_PM(g: ref Global, argl: list of string)
+{
+ code := "P";
+ if (argl != nil)
+ code = hd argl;
+ case code {
+ * => # includes "1" and "P"
+ g.pm = "<B>Lucent Technologies - Proprietary</B>\n" +
+ "<BR>Use pursuant to Company Instructions.\n";
+ "2" or "RS" =>
+ g.pm = "<B>Lucent Technologies - Proprietary (Restricted)</B>\n" +
+ "<BR>Solely for authorized persons having a need to know\n" +
+ "<BR>pursuant to Company Instructions.\n";
+ "3" or "RG" =>
+ g.pm = "<B>Lucent Technologies - Proprietary (Registered)</B>\n" +
+ "<BR>Solely for authorized persons having a need to know\n" +
+ "<BR>and subject to cover sheet instructions.\n";
+ "4" or "CP" =>
+ g.pm = "SEE PROPRIETARY NOTICE ON COVER PAGE\n";
+ "5" or "CR" =>
+ g.pm = "Copyright xxxx Lucent Technologies\n" + # should fill in the year from the date register
+ "<BR>All Rights Reserved.\n";
+ "6" or "UW" =>
+ g.pm = "THIS DOCUMENT CONTAINS PROPRIETARY INFORMATION OF\n" +
+ "<BR>LUCENT TECHNOLOGIES INC. AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN\n" +
+ "<BR>ACCORDANCE WITH APPLICABLE AGREEMENTS.\n" +
+ "<BR>Unpublished & Not for Publication\n";
+ }
+}
+
+g_PP(g: ref Global)
+{
+ closel(g);
+ reset_font(g);
+ p := g.softp();
+ if (p != nil)
+ g.print(p);
+ g.sop = true;
+ g.broken = true;
+}
+
+g_RE(g: ref Global)
+{
+ g.print("</DL>\n");
+ g.indents--;
+ g.broken = true;
+}
+
+g_RS(g: ref Global)
+{
+ g.print("<DL>\n<DT><DD>");
+ g.indents++;
+ g.broken = true;
+}
+
+g_SH(g: ref Global, argl: list of string)
+{
+ closeall(g, 1); # .SH is top-level list item
+ if (g.example)
+ g_EE(g);
+ if (g.fill && ! g.sop)
+ g.print("<P>");
+ g.print("<DT><H4>");
+ printargs(g, argl);
+ g.print("</H4>\n");
+ g.print("<DD>\n");
+ g.sop = true;
+ g.broken = true;
+}
+
+g_SM(g: ref Global, argl: list of string)
+{
+ g.def_sm++; # can't use def_goobie, lest we collide with a deferred font macro
+ if (argl == nil)
+ return;
+ g.print(sprint("<FONT SIZE=-%d>", g.def_sm));
+ printargs(g, argl);
+ g.print("</FONT>\n");
+ g.def_sm = 0;
+}
+
+g_sp(g: ref Global, argl: list of string)
+{
+ if (g.sop && g.fill)
+ return;
+ count := 1;
+ if (argl != nil) {
+ rcount := real hd argl;
+ count = int rcount; # may be 0 (e.g., ".sp .5")
+ if (count == 0 && rcount > 0.0)
+ count = 1; # force whitespace for fractional lines
+ }
+ g.dobreak();
+ for (i := 0; i < count; i++)
+ g.print("&nbsp;<BR>\n");
+ g.broken = true;
+ g.sop = count > 0;
+}
+
+g_SS(g: ref Global, argl: list of string)
+{
+ closeall(g, 1);
+ g.indents++;
+ g.print(g.softp() + "<DL><DT><FONT SIZE=3><B>");
+ printargs(g, argl);
+ g.print("</B></FONT>\n");
+ g.print("<DD>\n");
+ g.sop = true;
+ g.broken = true;
+}
+
+g_TH(g: ref Global, argl: list of string)
+{
+ if (g.oldstyle.names && len argl > 2)
+ argl = hd argl :: hd tl argl :: nil; # ignore extra .TH args on pages in oldstyle trees
+ case len argl {
+ 0 =>
+ g.oldstyle.fmt = true;
+ title(g, sprint("%s", g.href.title), false);
+ 1 =>
+ g.oldstyle.fmt = true;
+ title(g, sprint("%s", hd argl), false); # any pages use this form?
+ 2 =>
+ g.oldstyle.fmt = true;
+ g.thisone.page = hd argl;
+ g.thisone.mtype = hd tl argl;
+ g.mk_href_man(hd argl, true);
+ g.mk_href_mtype(nil, hd tl argl);
+ title(g, sprint("%s(%s)", g.href.man, g.href.mtype), false);
+ * =>
+ g.oldstyle.fmt = false;
+ chap := hd tl tl argl;
+ g.mk_href_chap(chap);
+ g.mk_href_man(hd argl, false);
+ g.mk_href_mtype(chap, hd tl argl);
+ title(g, sprint("%s/%s/%s(%s)", g.href.title, g.href.chap, g.href.man, g.href.mtype), false);
+ }
+ g.print("[<a href=\"../index.html\">manual index</a>]");
+ g.print("[<a href=\"INDEX.html\">section index</a>]<p>");
+ g.print("<DL>\n"); # whole man page is just one big list
+ g.indents = 1;
+ g.sop = true;
+ g.broken = true;
+}
+
+dohangingdt(g: ref Global)
+{
+ case g.hangingdt {
+ 3 =>
+ g.hangingdt--;
+ 2 =>
+ g.print("<DD>");
+ g.hangingdt = 0;
+ g.broken = true;
+ }
+}
+
+# close a list, if there's one active
+closel(g: ref Global)
+{
+ case g.list_type {
+ Lordered =>
+ g.print("</OL>\n");
+ g.broken = true;
+ Lunordered =>
+ g.print("</UL>\n");
+ g.broken = true;
+ Lother or Ldef =>
+ g.print("</DL>\n");
+ g.broken = true;
+ }
+ g.list_type = Lnone;
+}
+
+closeall(g: ref Global, level: int)
+{
+ closel(g);
+ reset_font(g);
+ while (g.indents > level) {
+ g.indents--;
+ g.print("</DL>\n");
+ g.broken = true;
+ }
+}
+
+#
+# Show last revision date for a file.
+#
+rev(g: ref Global, filebuf: ref Bufio->Iobuf)
+{
+ if (g.mtime == 0) {
+ (err, info) := sys->fstat(filebuf.fd);
+ if (! err)
+ g.mtime = info.mtime;
+ }
+ if (g.mtime != 0) {
+ g.print("<P><TABLE width=\"100%\" border=0 cellpadding=10 cellspacing=0 bgcolor=\"#E0E0E0\">\n");
+ g.print("<TR>");
+ g.print(sprint("<TD align=left><FONT SIZE=-1>"));
+ g.print(sprint("%s(%s)", g.thisone.page, g.thisone.mtype));
+ g.print("</FONT></TD>\n");
+ g.print(sprint("<TD align=right><FONT SIZE=-1><I>Rev:&nbsp;&nbsp;%s</I></FONT></TD></TR></TABLE>\n",
+ dt->text(dt->gmt(g.mtime))));
+ }
+}
+
+#
+# Some font alternation macros are references to other man pages;
+# detect them (second arg contains balanced parens) and make them into hot links.
+#
+anchor(g: ref Global, f1, f2: string, argl: list of string)
+{
+ final := "";
+ link := false;
+ if (len argl == 2) {
+ (s, e) := str->splitl(hd tl argl, ")");
+ if (str->prefix("(", s) && e != nil) {
+ # emit href containing search for target first
+ # if numeric, do old style
+ link = true;
+ file := hd argl;
+ (chap, man) := split(httpunesc(file), '/');
+ if (man == nil) {
+ # given no explicit chapter prefix, use current chapter
+ man = chap;
+ chap = g.thisone.chap;
+ }
+ mtype := s[1:];
+ if (mtype == nil)
+ mtype = "-";
+ (n, toks) := sys->tokenize(mtype, "."); # Fix section 10
+ if (n > 1) mtype = hd toks;
+ g.print(sprint("<A href=\"../%s/%s.html\">", mtype, fixlink(man)));
+
+ #
+ # now generate the name the user sees, with terminal punctuation
+ # moved after the closing </A>.
+ #
+ if (len e > 1)
+ final = e[1:];
+ argl = hd argl :: s + ")" :: nil;
+ }
+ }
+ altfont(g, f1, f2, argl, false);
+ if (link) {
+ g.print("</A>");
+ font(g, f2, final :: nil);
+ } else
+ g.print("\n");
+}
+
+
+#
+# Fix up a link
+#
+
+fixlink(l: string): string
+{
+ ll := str->tolower(l);
+ if (ll == "copyright") ll = "1" + ll;
+ (a, b) := str->splitstrl(ll, "intro");
+ if (len b == 5) ll = a + "0" + b;
+ return ll;
+}
+
+
+#
+# output argl in font f
+#
+font(g: ref Global, f: string, argl: list of string)
+{
+ if (argl == nil) {
+ g.def_goobie = f;
+ return;
+ }
+ case f {
+ "L" => f = "TT";
+ "R" => f = nil;
+ }
+ if (f != nil) # nil == default (typically Roman)
+ g.print(sprint("<%s>", f));
+ printargs(g, argl);
+ if (f != nil)
+ g.print(sprint("</%s>", f));
+ g.print("\n");
+ g.prevfont = f;
+}
+
+#
+# output concatenated elements of argl, alternating between fonts f1 and f2
+#
+altfont(g: ref Global, f1, f2: string, argl: list of string, newline: int)
+{
+ reset_font(g);
+ if (argl == nil) {
+ g.def_goobie = f1;
+ return;
+ }
+ case f1 {
+ "L" => f1 = "TT";
+ "R" => f1 = nil;
+ }
+ case f2 {
+ "L" => f2 = "TT";
+ "R" => f2 = nil;
+ }
+ f := f1;
+ for (; argl != nil; argl = tl argl) {
+ if (f != nil)
+ g.print(sprint("<%s>%s</%s>", f, hd argl, f));
+ else
+ g.print(hd argl);
+ if (f == f1)
+ f = f2;
+ else
+ f = f1;
+ }
+ if (newline)
+ g.print("\n");
+ g.prevfont = f;
+}
+
+# not yet implemented
+map_font(nil: ref Global, nil: string)
+{
+}
+
+reset_font(g: ref Global)
+{
+ if (g.curfont != nil) {
+ g.print(sprint("</%s>", g.curfont));
+ g.prevfont = g.curfont;
+ g.curfont = nil;
+ }
+}
+
+printargs(g: ref Global, argl: list of string)
+{
+ for (; argl != nil; argl = tl argl)
+ if (tl argl != nil)
+ g.print(hd argl + " ");
+ else
+ g.print(hd argl);
+}
+
+# any parameter can be nil
+addhit(g: ref Global, chap, mtype, page: string)
+{
+ # g.print(sprint("Adding %s / %s (%s) . . .", chap, page, mtype)); # debug
+ # always keep a spare slot at the end
+ if (g.nhits >= len g.hits - 1)
+ g.hits = (array[len g.hits + 32] of Hit)[0:] = g.hits;
+ g.hits[g.nhits].glob = chap + " " + mtype + " " + page;
+ g.hits[g.nhits].chap = chap;
+ g.hits[g.nhits].mtype = mtype;
+ g.hits[g.nhits++].page = page;
+}
+
+Global.dobreak(g: self ref Global)
+{
+ if (! g.broken) {
+ g.broken = true;
+ g.print("<BR>\n");
+ }
+}
+
+Global.print(g: self ref Global, s: string)
+{
+ g.bufio->g.bout.puts(s);
+ if (g.sop || g.broken) {
+ # first non-white space, non-HTML we print takes us past the start of the paragraph & line
+ # (or even white space, if we're in no-fill mode)
+ for (i := 0; i < len s; i++) {
+ case s[i] {
+ '<' =>
+ while (++i < len s && s[i] != '>')
+ ;
+ continue;
+ ' ' or '\t' or '\n' =>
+ if (g.fill)
+ continue;
+ }
+ g.sop = false;
+ g.broken = false;
+ break;
+ }
+ }
+}
+
+Global.softbr(g: self ref Global): string
+{
+ if (g.broken)
+ return nil;
+ g.broken = true;
+ return "<BR>";
+}
+
+# provide a paragraph marker, unless we're already at the start of a section
+Global.softp(g: self ref Global): string
+{
+ if (g.sop)
+ return nil;
+ else if (! g.ipd)
+ return "<BR>";
+ if (g.fill)
+ return "<P>";
+ else
+ return "<P style=\"white-space: pre\">";
+}
+
+#
+# Get next logical character. Expand it with escapes.
+#
+getnext(g: ref Global): string
+{
+ iob := g.bufio;
+ Iobuf: import iob;
+
+ font: string;
+ token: string;
+ bin := g.bin;
+
+ g.sol = (g.lastc == '\n');
+
+ c := bin.getc();
+ if (c < 0)
+ return nil;
+ g.lastc = c;
+ if (c >= Runeself) {
+ for (i := 0; i < len Entities; i++)
+ if (Entities[i].value == c)
+ return Entities[i].name;
+ return sprint("&#%d;", c);
+ }
+ case c {
+ '<' =>
+ return "&lt;";
+ '>' =>
+ return "&gt;";
+ '\\' =>
+ c = bin.getc();
+ if (c < 0)
+ return nil;
+ g.lastc = c;
+ case c {
+
+ # chars to ignore
+ '|' or '&' or '^' =>
+ return getnext(g);
+
+ # ignore arg
+ 'k' =>
+ nil = bin.getc();
+ return getnext(g);
+
+ # defined strings
+ '*' =>
+ case bin.getc() {
+ 'R' =>
+ return "&#174;";
+ }
+ return getnext(g);
+
+ # special chars
+ '(' =>
+ token[0] = bin.getc();
+ token[1] = bin.getc();
+ for (i := 0; i < len tspec; i++)
+ if (token == tspec[i].name)
+ return tspec[i].value;
+ return "&#191;";
+ 'c' =>
+ c = bin.getc();
+ if (c < 0)
+ return nil;
+ else if (c == '\n') {
+ g.lastc = c;
+ g.sol = true;
+ token[0] = bin.getc();
+ return token;
+ }
+ # DEBUG: should there be a "return xxx" here?
+ 'e' =>
+ return "\\";
+ 'f' =>
+ g.lastc = c = bin.getc();
+ if (c < 0)
+ return nil;
+ case c {
+ '2' or 'I' =>
+ font = "I";
+ '3' or 'B' =>
+ font = "B";
+ '5' or 'L' =>
+ font = "TT";
+ 'P' =>
+ font = g.prevfont;
+ * => # includes '1' and 'R'
+ font = nil;
+ }
+# There are serious problems with this. We don't know the fonts properly at this stage.
+# g.prevfont = g.curfont;
+# g.curfont = font;
+# if (g.prevfont != nil)
+# token = sprint("</%s>", g.prevfont);
+# if (g.curfont != nil)
+# token += sprint("<%s>", g.curfont);
+ if (token == nil)
+ return " "; # shouldn't happen - maybe a \fR inside a font macro - just do something!
+ return token;
+ 's' =>
+ sign := '+';
+ size := 0;
+ relative := false;
+ getsize:
+ for (;;) {
+ c = bin.getc();
+ if (c < 0)
+ return nil;
+ case c {
+ '+' =>
+ relative = true;
+ '-' =>
+ sign = '-';
+ relative = true;
+ '0' to '9' =>
+ size = size * 10 + (c - '0');
+ * =>
+ bin.ungetc();
+ break getsize;
+ }
+ g.lastc = c;
+ }
+ if (size == 0)
+ token = "</FONT>";
+ else if (relative)
+ token = sprint("<FONT SIZE=%c%d>", sign, size);
+ else
+ token = sprint("<FONT SIZE=%d>", size);
+ return token;
+ }
+ }
+ token[0] = c;
+ return token;
+}
+
+#
+# Return strings before and after the left-most instance of separator;
+# (s, nil) if no match or separator is last char in s.
+#
+split(s: string, sep: int): (string, string)
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] == sep)
+ return (s[:i], s[i+1:]); # s[len s:] is a valid slice, with value == nil
+ return (s, nil);
+}
+
+Global_init(): ref Global
+{
+ g := ref Global;
+ g.bufio = load Bufio Bufio->PATH;
+ g.chaps = array[20] of Chaps;
+ g.types = array[20] of Types;
+ g.mantitle = "";
+ g.href.title = g.mantitle; # ??
+ g.mtime = 0;
+ g.nhits = 0;
+ g.oldstyle.names = false;
+ g.oldstyle.fmt = false;
+ g.topname = "System";
+ g.list_type = Lnone;
+ g.def_sm = 0;
+ g.hangingdt = 0;
+ g.indents = 0;
+ g.sop = true;
+ g.broken = true;
+ g.ipd = true;
+ g.fill = true;
+ g.example = false;
+ g.pre = false;
+ g.lastc = '\n';
+ return g;
+}
+
+Global.mk_href_chap(g: self ref Global, chap: string)
+{
+ if (chap != nil)
+ g.href.chap = sprint("<A href=\"%s/%s?man=*\"><B>%s</B></A>", g.mandir, chap, chap);
+}
+
+Global.mk_href_man(g: self ref Global, man: string, oldstyle: int)
+{
+ rman := man;
+ if (oldstyle)
+ rman = str->tolower(man); # compensate for tradition of putting titles in all CAPS
+ g.href.man = sprint("<A href=\"%s?man=%s\"><B>%s</B></A>", g.mandir, rman, man);
+}
+
+Global.mk_href_mtype(g: self ref Global, chap, mtype: string)
+{
+ g.href.mtype = sprint("<A href=\"%s/%s/%s\"><B>%s</B></A>", g.mandir, chap, mtype, mtype);
+}
+
+# We assume that anything >= Runeself is already in UTF.
+#
+httpunesc(s: string): string
+{
+ t := "";
+ for (i := 0; i < len s; i++) {
+ c := s[i];
+ if (c == '&' && i + 1 < len s) {
+ (char, rem) := str->splitl(s[i+1:], ";");
+ if (rem == nil)
+ break; # require the terminating ';'
+ if (char == nil)
+ continue;
+ if (char[0] == '#' && len char > 1) {
+ c = int char[1:];
+ i += len char;
+ if (c < 256 && c >= 161) {
+ t[len t] = Entities[c-161].value;
+ continue;
+ }
+ } else {
+ for (j := 0; j < len Entities; j++)
+ if (Entities[j].name == char)
+ break;
+ if (j < len Entities) {
+ i += len char;
+ t[len t] = Entities[j].value;
+ continue;
+ }
+ }
+ }
+ t[len t] = c;
+ }
+ return t;
+}
+
+
+
+title(g: ref Global, t: string, search: int)
+{
+ if(search)
+ ; # not yet used
+ g.print("<HTML><HEAD>\n");
+ g.print(sprint("<TITLE>Inferno's %s</TITLE>\n", demark(t)));
+ g.print("</HEAD>\n");
+ g.print("<BODY bgcolor=\"#FFFFFF\">\n");
+
+}
diff --git a/appl/cmd/shutdown.b b/appl/cmd/shutdown.b
deleted file mode 100644
index 8eb7a86c..00000000
--- a/appl/cmd/shutdown.b
+++ /dev/null
@@ -1,72 +0,0 @@
-implement Shutdown;
-
-include "sys.m";
-sys: Sys;
-FD: import Sys;
-stderr: ref FD;
-
-include "draw.m";
-Context: import Draw;
-
-sysctl: con "/dev/sysctl";
-reboot: con "reboot";
-halt: con "halt";
-
-Shutdown: module
-{
- init: fn(ctxt: ref Context, argv: list of string);
-};
-
-rflag: int;
-hflag: int;
-
-init(nil: ref Context, argv: list of string)
-{
- sys = load Sys Sys->PATH;
-
- stderr = sys->fildes(2);
-
- argv = tl argv;
- if(len argv < 1)
- usage();
-
- while(argv != nil && len hd argv && (arg := hd argv)[0] == '-' && len arg > 1){
- case arg[1] {
- 'r' =>
- rflag = 1;
- 'h' =>
- hflag = 1;
- }
- argv = tl argv;
- }
-
- if(rflag == 0 && hflag == 0)
- usage();
-
- if(rflag == 1 && hflag == 1)
- usage();
-
- fd := sys->open(sysctl, sys->OWRITE);
- if(fd == nil) {
- sys->fprint(stderr, "shutdown: %r\n");
- exit;
- }
-
- if(rflag == 1)
- if (sys->write(fd, array of byte reboot, len reboot) < 0) {
- sys->fprint(stderr, "shutdown: write failed: %r\n");
- exit;
- }
-
- if(hflag == 1)
- if (sys->write(fd, array of byte halt, len halt) < 0) {
- sys->fprint(stderr, "shutdown: write failed: %r\n");
- exit;
- }
-}
-
-usage()
-{
- sys->fprint(stderr, "usage: shutdown -r | -h\n");
- exit;
-}