xref: /plan9/sys/src/cmd/fmt.c (revision 6a9fc400c33447ef5e1cda7185cb4de2c8e8010e)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <ctype.h>
5 
6 /*
7  * block up paragraphs, possibly with indentation
8  */
9 
10 int extraindent = 0;			/* how many spaces to indent all lines */
11 int indent = 0;			/* current value of indent, before extra indent */
12 int length = 70;		/* how many columns per output line */
13 int maxtab = 8;
14 Biobuf bin;
15 Biobuf bout;
16 
17 typedef struct Word Word;
18 struct Word{
19 	int	indent;
20 	char	text[1];
21 };
22 
23 void	fmt(void);
24 
25 void
26 usage(void)
27 {
28 	fprint(2, "usage: %s [-j] [-i indent] [-l length] [file...]\n", argv0);
29 	exits("usage");
30 }
31 
32 void
33 main(int argc, char **argv)
34 {
35 	int i, f;
36 	char *s, *err;
37 
38 	ARGBEGIN{
39 	case 'i':
40 		extraindent = atoi(EARGF(usage()));
41 		break;
42 	case 'w':
43 	case 'l':
44 		length = atoi(EARGF(usage()));
45 		break;
46 	default:
47 		usage();
48 	}ARGEND
49 
50 	if(length <= indent){
51 		fprint(2, "%s: line length<=indentation\n", argv0);
52 		exits("length");
53 	}
54 
55 	s=getenv("tabstop");
56 	if(s!=nil && atoi(s)>0)
57 		maxtab=atoi(s);
58 	err = nil;
59 	Binit(&bout, 1, OWRITE);
60 	if(argc <= 0){
61 		Binit(&bin, 0, OREAD);
62 		fmt();
63 	}else{
64 		for(i=0; i<argc; i++){
65 			f = open(argv[i], OREAD);
66 			if(f < 0){
67 				fprint(2, "%s: can't open %s: %r\n", argv0, argv[i]);
68 				err = "open";
69 			}else{
70 				Binit(&bin, f, OREAD);
71 				fmt();
72 				Bterm(&bin);
73 				if(i != argc-1)
74 					Bputc(&bout, '\n');
75 			}
76 		}
77 	}
78 	exits(err);
79 }
80 
81 int
82 indentof(char **linep)
83 {
84 	int i, ind;
85 	char *line;
86 
87 	ind = 0;
88 	line = *linep;
89 	for(i=0; line[i]; i++)
90 		switch(line[i]){
91 		default:
92 			*linep = line;
93 			return ind;
94 		case ' ':
95 			ind++;
96 			break;
97 		case '\t':
98 			ind += maxtab;
99 			ind -= ind%maxtab;
100 			break;
101 		}
102 
103 	/* plain white space doesn't change the indent */
104 	*linep = "";
105 	return indent;
106 }
107 
108 Word**
109 addword(Word **words, int *nwordp, char *s, int l, int indent)
110 {
111 	Word *w;
112 
113 	w = malloc(sizeof(Word)+l+1);
114 	memmove(w->text, s, l);
115 	w->text[l] = '\0';
116 	w->indent = indent;
117 	words = realloc(words, (*nwordp+1)*sizeof(Word*));
118 	words[(*nwordp)++] = w;
119 	return words;
120 }
121 
122 Word**
123 parseline(char *line, Word **words, int *nwordp)
124 {
125 	int ind, l, blankline;
126 
127 	ind = indentof(&line);
128 	indent = ind;
129 	blankline = 1;
130 	for(;;){
131 		/* find next word */
132 		while(*line==' ' || *line=='\t')
133 			line++;
134 		if(*line == '\0'){
135 			if(blankline)
136 				return addword(words, nwordp, "", 0, -1);
137 			break;
138 		}
139 		blankline = 0;
140 		/* how long is this word? */
141 		for(l=0; line[l]; l++)
142 			if(line[l]==' ' || line[l]=='\t')
143 				break;
144 		words = addword(words, nwordp, line, l, indent);
145 		line += l;
146 	}
147 	return words;
148 }
149 
150 void
151 printindent(int w)
152 {
153 	while(w >= maxtab){
154 		Bputc(&bout, '\t');
155 		w -= maxtab;
156 	}
157 	while(w > 0){
158 		Bputc(&bout, ' ');
159 		w--;
160 	}
161 }
162 
163 /* give extra space if word ends with period, etc. */
164 int
165 nspaceafter(char *s)
166 {
167 	int n;
168 
169 	n = strlen(s);
170 	if(n < 2)
171 		return 1;
172 	if(strchr(".!?", s[n-1]) != nil)
173 		return 2;
174 	return 1;
175 }
176 
177 
178 void
179 printwords(Word **w, int nw)
180 {
181 	int i, j, col, nsp;
182 
183 	/* one output line per loop */
184 	for(i=0; i<nw; ){
185 		/* if it's a blank line, print it */
186 		if(w[i]->indent == -1){
187 			Bputc(&bout, '\n');
188 			if(++i == nw)	/* out of words */
189 				break;
190 		}
191 		/* emit leading indent */
192 		col = extraindent+w[i]->indent;
193 		printindent(col);
194 		/* emit words until overflow; always emit at least one word */
195 		for(;;){
196 			Bprint(&bout, "%s", w[i]->text);
197 			col += strlen(w[i]->text);
198 			if(++i == nw)
199 				break;	/* out of words */
200 			if(w[i]->indent != w[i-1]->indent)
201 				break;	/* indent change */
202 			nsp = nspaceafter(w[i-1]->text);
203 			if(col+nsp+strlen(w[i]->text) > extraindent+length)
204 				break;	/* fold line */
205 			for(j=0; j<nsp; j++)
206 				Bputc(&bout, ' ');	/* emit space; another word will follow */
207 			col += nsp;
208 		}
209 		/* emit newline */
210 		Bputc(&bout, '\n');
211 	}
212 }
213 
214 void
215 fmt(void)
216 {
217 	char *s;
218 	int i, nw;
219 	Word **w;
220 
221 	nw = 0;
222 	w = nil;
223 	while((s = Brdstr(&bin, '\n', 1)) != nil){
224 		w = parseline(s, w, &nw);
225 		free(s);
226 	}
227 	printwords(w, nw);
228 	for(i=0; i<nw; i++)
229 		free(w[i]);
230 	free(w);
231 }
232