xref: /inferno-os/appl/cmd/fmt.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Fmt;
2
3#
4#	Copyright © 2002 Lucent Technologies Inc.
5#	based on the Plan 9 command; subject to the Lucent Public License 1.02
6#	this Vita Nuova variant uses Limbo channels and processes to avoid accumulating words
7#
8
9#
10#  block up paragraphs, possibly with indentation
11#
12
13include "sys.m";
14	sys: Sys;
15
16include "draw.m";
17
18include "bufio.m";
19	bufio: Bufio;
20	Iobuf: import bufio;
21
22include "arg.m";
23
24Fmt: module
25{
26	init: fn(nil: ref Draw->Context, nil: list of string);
27};
28
29extraindent := 0;	# how many spaces to indent all lines
30indent := 0;	# current value of indent, before extra indent
31length := 70;	# how many columns per output line
32join := 1;	# can lines be joined?
33maxtab := 8;
34bout: ref Iobuf;
35
36Word: adt {
37	text:	string;
38	indent:	int;
39	bol:	int;
40};
41
42init(nil: ref Draw->Context, args: list of string)
43{
44	sys = load Sys Sys->PATH;
45	bufio = load Bufio Bufio->PATH;
46	arg := load Arg Arg->PATH;
47
48	arg->init(args);
49	arg->setusage("fmt [-j] [-i indent] [-l length] [file...]");
50	while((c := arg->opt()) != 0)
51		case(c){
52		'i' =>
53			extraindent = int arg->earg();
54		'j' =>
55			join = 0;
56		'w' or 'l' =>
57			length = int arg->earg();
58		* =>
59			arg->usage();
60		}
61	args = arg->argv();
62	if(length <= extraindent){
63		sys->fprint(sys->fildes(2), "fmt: line length<=indentation\n");
64		raise "fail:length";
65	}
66	arg = nil;
67
68	err := "";
69	bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
70	if(args == nil){
71		bin := bufio->fopen(sys->fildes(0), Bufio->OREAD);
72		fmt(bin);
73	}else
74		for(; args != nil; args = tl args){
75			bin := bufio->open(hd args, Bufio->OREAD);
76			if(bin == nil){
77				sys->fprint(sys->fildes(2), "fmt: can't open %s: %r\n", hd args);
78				err = "open";
79			}else{
80				fmt(bin);
81				if(tl args != nil)
82					bout.putc('\n');
83			}
84		}
85	bout.flush();
86	if(err != nil)
87		raise "fail:"+err;
88}
89
90fmt(f: ref Iobuf)
91{
92	words := chan of ref Word;
93	spawn parser(f, words);
94	printwords(words);
95}
96
97parser(f: ref Iobuf, words: chan of ref Word)
98{
99	while((s := f.gets('\n')) != nil){
100		if(s[len s-1] == '\n')
101			s = s[0:len s-1];
102		parseline(s, words);
103	}
104	words <-= nil;
105}
106
107parseline(line: string, words: chan of ref Word)
108{
109	ind: int;
110	(line, ind) = indentof(line);
111	indent = ind;
112	bol := 1;
113	for(i:=0; i < len line;){
114		# find next word
115		if(line[i] == ' ' || line[i] == '\t'){
116			i++;
117			continue;
118		}
119		# where does this word end?
120		for(l:=i; l < len line; l++)
121			if(line[l]==' ' || line[l]=='\t')
122				break;
123		words <-= ref Word(line[i:l], indent, bol);
124		bol = 0;
125		i = l;
126	}
127	if(bol)
128		words <-= ref Word("", -1, bol);
129}
130
131indentof(line: string): (string, int)
132{
133	ind := 0;
134	for(i:=0; i < len line; i++)
135		case line[i] {
136		' ' =>
137			ind++;
138		'\t' =>
139			ind += maxtab;
140			ind -= ind%maxtab;
141		* =>
142			return (line, ind);
143		}
144	# plain white space doesn't change the indent
145	return (line, indent);
146}
147
148printwords(words: chan of ref Word)
149{
150	# one output line per loop
151	nw := <-words;
152	while((w := nw) != nil){
153		# if it's a blank line, print it
154		if(w.indent == -1){
155			bout.putc('\n');
156			nw = <-words;
157			continue;
158		}
159		# emit leading indent
160		col := extraindent+w.indent;
161		printindent(col);
162		# emit words until overflow; always emit at least one word
163		for(n:=0;; n++){
164			bout.puts(w.text);
165			col += len w.text;
166			if((nw = <-words) == nil)
167				break;	# out of words
168			if(nw.indent != w.indent)
169				break;	# indent change
170			nsp := nspaceafter(w.text);
171			if(col+nsp+len nw.text > extraindent+length)
172				break;	# fold line
173			if(!join && nw.bol)
174				break;
175			for(j:=0; j<nsp; j++)
176				bout.putc(' ');	# emit space; another word will follow
177			col += nsp;
178			w = nw;
179		}
180		bout.putc('\n');
181	}
182}
183
184printindent(w: int)
185{
186	while(w >= maxtab){
187		bout.putc('\t');
188		w -= maxtab;
189	}
190	while(--w >= 0)
191		bout.putc(' ');
192}
193
194# give extra space if word ends with punctuation
195nspaceafter(s: string): int
196{
197	if(len s < 2)
198		return 1;
199	if(len s < 4 && s[0] >= 'A' && s[0] <= 'Z')
200		return 1;	# assume it's a title, not full stop
201	if((c := s[len s-1]) == '.' || c == '!' || c == '?')
202		return 2;
203	return 1;
204}
205