diff options
Diffstat (limited to 'appl/cmd/fmt.b')
| -rwxr-xr-x | appl/cmd/fmt.b | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/appl/cmd/fmt.b b/appl/cmd/fmt.b new file mode 100755 index 00000000..337f9fd2 --- /dev/null +++ b/appl/cmd/fmt.b @@ -0,0 +1,204 @@ +implement Fmt; + +# +# Copyright © 2002 Lucent Technologies Inc. +# based on the Plan 9 command; subject to the Lucent Public License 1.02 +# this Vita Nuova variant uses Limbo channels and processes to avoid accumulating words +# + +# +# block up paragraphs, possibly with indentation +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Fmt: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +extraindent := 0; # how many spaces to indent all lines +indent := 0; # current value of indent, before extra indent +length := 70; # how many columns per output line +join := 1; # can lines be joined? +maxtab := 8; +bout: ref Iobuf; + +Word: adt { + text: string; + indent: int; + bol: int; +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + arg := load Arg Arg->PATH; + + arg->init(args); + arg->setusage("fmt [-j] [-i indent] [-l length] [file...]"); + while((c := arg->opt()) != 0) + case(c){ + 'i' => + extraindent = int arg->earg(); + 'j' => + join = 0; + 'w' or 'l' => + length = int arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + if(length <= extraindent){ + sys->fprint(sys->fildes(2), "fmt: line length<=indentation\n"); + raise "fail:length"; + } + arg = nil; + + err := ""; + bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + if(args == nil){ + bin := bufio->fopen(sys->fildes(0), Bufio->OREAD); + fmt(bin); + }else + for(; args != nil; args = tl args){ + bin := bufio->open(hd args, Bufio->OREAD); + if(bin == nil){ + sys->fprint(sys->fildes(2), "fmt: can't open %s: %r\n", hd args); + err = "open"; + }else{ + fmt(bin); + if(tl args != nil) + bout.putc('\n'); + } + } + bout.flush(); + if(err != nil) + raise "fail:"+err; +} + +fmt(f: ref Iobuf) +{ + words := chan of ref Word; + spawn parser(f, words); + printwords(words); +} + +parser(f: ref Iobuf, words: chan of ref Word) +{ + while((s := f.gets('\n')) != nil){ + if(s[len s-1] == '\n') + s = s[0:len s-1]; + parseline(s, words); + } + words <-= nil; +} + +parseline(line: string, words: chan of ref Word) +{ + ind: int; + (line, ind) = indentof(line); + indent = ind; + bol := 1; + for(i:=0; i < len line;){ + # find next word + if(line[i] == ' ' || line[i] == '\t'){ + i++; + continue; + } + # where does this word end? + for(l:=i; l < len line; l++) + if(line[l]==' ' || line[l]=='\t') + break; + words <-= ref Word(line[i:l], indent, bol); + bol = 0; + i = l; + } + if(bol) + words <-= ref Word("", -1, bol); +} + +indentof(line: string): (string, int) +{ + ind := 0; + for(i:=0; i < len line; i++) + case line[i] { + ' ' => + ind++; + '\t' => + ind += maxtab; + ind -= ind%maxtab; + * => + return (line, ind); + } + # plain white space doesn't change the indent + return (line, indent); +} + +printwords(words: chan of ref Word) +{ + # one output line per loop + nw := <-words; + while((w := nw) != nil){ + # if it's a blank line, print it + if(w.indent == -1){ + bout.putc('\n'); + nw = <-words; + continue; + } + # emit leading indent + col := extraindent+w.indent; + printindent(col); + # emit words until overflow; always emit at least one word + for(n:=0;; n++){ + bout.puts(w.text); + col += len w.text; + if((nw = <-words) == nil) + break; # out of words + if(nw.indent != w.indent) + break; # indent change + nsp := nspaceafter(w.text); + if(col+nsp+len nw.text > extraindent+length) + break; # fold line + if(!join && nw.bol) + break; + for(j:=0; j<nsp; j++) + bout.putc(' '); # emit space; another word will follow + col += nsp; + w = nw; + } + bout.putc('\n'); + } +} + +printindent(w: int) +{ + while(w >= maxtab){ + bout.putc('\t'); + w -= maxtab; + } + while(--w >= 0) + bout.putc(' '); +} + +# give extra space if word ends with punctuation +nspaceafter(s: string): int +{ + if(len s < 2) + return 1; + if(len s < 4 && s[0] >= 'A' && s[0] <= 'Z') + return 1; # assume it's a title, not full stop + if((c := s[len s-1]) == '.' || c == '!' || c == '?') + return 2; + return 1; +} |
