💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Vgmi › files › 62fcf459d7236865e8f54b377b85b… captured on 2023-12-28 at 15:44:22. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
0 /*
1 * ISC License
2 * Copyright (c) 2023 RMF <rawmonk@firemail.cc>
3 */
4 #include <stdio.h>
5 #include <stdint.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <math.h>
10 #include "macro.h"
11 #include "client.h"
12 #define PAGE_INTERNAL
13 #include "page.h"
14 #include "request.h"
15 #define PARSER_INTERNAL
16 #include "parser.h"
17 #include "termbox.h"
18 #include "wcwidth.h"
19 #include "error.h"
20 #include "strlcpy.h"
21 #include "strnstr.h"
22 #include "utf8.h"
23
24 #define TAB_SIZE 4
25
26 #define BLOCK_SIZE 4096
27
28 enum {
29 LINE_TEXT,
30 LINE_HEADER,
31 LINE_SUBHEADER,
32 LINE_SUBSUBHEADER,
33 LINE_BLOCKQUOTE,
34 LINE_LIST,
35 LINE_LINK,
36 LINE_PREFORMATTED
37 };
38
39 static int colorFromLine(int line) {
40 switch (line) {
41 case LINE_TEXT: return TB_DEFAULT;
42 case LINE_HEADER: return TB_RED;
43 case LINE_SUBHEADER: return TB_GREEN;
44 case LINE_SUBSUBHEADER: return TB_YELLOW;
45 case LINE_BLOCKQUOTE: return TB_MAGENTA;
46 case LINE_LIST: return TB_CYAN;
47 case LINE_LINK: return TB_BLUE;
48 case LINE_PREFORMATTED: return TB_WHITE;
49 default: return TB_DEFAULT;
50 }
51 }
52
53 static int nextHeader(int header) {
54 switch (header) {
55 case LINE_TEXT: return LINE_HEADER;
56 case LINE_HEADER: return LINE_SUBHEADER;
57 case LINE_SUBHEADER: return LINE_SUBSUBHEADER;
58 default: return header;
59 }
60 }
61
62 /* TODO: verify that writecell and writeto return 0 */
63 int gemtext_parse_link(int in, size_t *pos, size_t length, int *links,
64 struct termwriter *termwriter, uint32_t *ch) {
65
66 char buf[32];
67 struct page_cell cell = {0}, cells[MAX_URL] = {0};
68 int i, j, rewrite;
69
70 /* readnext till finding not a space */
71 while (*pos < length) {
72 if (readnext(in, &cell.codepoint, pos))
73 return -1;
74 if (!WHITESPACE(cell.codepoint)) break;
75 }
76
77 /* if ch is newline, abort since not a valid link */
78 if (cell.codepoint == '\n') {
79 writeto(termwriter, "=>", colorFromLine(LINE_TEXT), 0, *pos);
80 *ch = '\n';
81 return 0;
82 }
83 /* readnext till find a space or newline */
84 cells[0] = cell;
85 for (i = 1; i < MAX_URL && *pos < length; i++) {
86 if (readnext(in, &cells[i].codepoint, pos))
87 return -1;
88 if (!renderable(cells[i].codepoint)) {
89 i--;
90 continue;
91 }
92 if (SEPARATOR(cells[i].codepoint)) break;
93 }
94
95 if (i == MAX_URL) { /* invalid url */
96 *ch = 0;
97 /* read remaining bytes of the invalid url */
98 while (*ch != '\n' && *pos < length)
99 if (readnext(in, ch, pos)) return -1;
100 return 0;
101 }
102
103 /* if finding space ignore what was read */
104 rewrite = (cells[i].codepoint == '\n' || *pos >= length) ? i : 0;
105
106 *ch = rewrite ? '\n' : 0;
107 for (j = 0; !rewrite && j < MAX_URL && *pos < length; j++) {
108 if (readnext(in, ch, pos)) return -1;
109 if (!WHITESPACE(*ch)) {
110 if (*ch == '\n') rewrite = i;
111 break;
112 }
113 }
114
115 (*links)++;
116
117 j = snprintf(V(buf), "[%d]", *links);
118 writeto(termwriter, buf, colorFromLine(LINE_LINK), *links, *pos);
119
120 cell.color = colorFromLine(LINE_TEXT);
121 cell.width = 1;
122 cell.link = 0;
123 cell.codepoint = ' ';
124 for (; j < 6; j++) {
125 writecell(termwriter, cell, *pos);
126 }
127
128 if (ch && !rewrite) {
129 cell.codepoint = *ch;
130 cell.width = mk_wcwidth(*ch);
131 writecell(termwriter, cell, *pos);
132 }
133
134 /* if finding newline reprint what was read */
135 for (j = 0; j < rewrite; j++) {
136 cells[j].color = colorFromLine(LINE_TEXT);
137 cells[j].width = mk_wcwidth(cells[j].codepoint);
138 cells[j].link = 0;
139 cells[j].special = 0;
140 writecell(termwriter, cells[j], *pos);
141 }
142
143 return 0;
144 }
145
146 #define PARSE_LINE_COMPLETED 0
147 #define PARSE_LINE_SKIP 1
148 #define PARSE_LINE_BROKEN 2
149 int gemtext_parse_line(int in, size_t *pos, size_t length, int *_color,
150 int *links, int *preformatted,
151 struct termwriter *termwriter, uint32_t *last) {
152
153 int ret;
154 int color = *_color, header = 0, link = 0, preformat = 0, start = 1;
155
156 if (*preformatted) color = colorFromLine(LINE_PREFORMATTED);
157
158 ret = PARSE_LINE_COMPLETED;
159 while (*pos < length) {
160
161 struct page_cell cell = {0};
162
163 if (*last) {
164 cell.codepoint = *last;
165 *last = 0;
166 } else if (readnext(in, &cell.codepoint, pos)) {
167 return -1;
168 }
169 if (preformat == -1) {
170 if (cell.codepoint == '\n') {
171 ret = PARSE_LINE_SKIP;
172 *last = 0;
173 break;
174 }
175 preformat = 0;
176 }
177 if (cell.codepoint == '\n') break;
178 cell.width = mk_wcwidth(cell.codepoint);
179 if (cell.width >= termwriter->width) cell.width = 1;
180 if (!renderable(cell.codepoint)) {
181 struct page_cell cell = {0};
182 cell.special = PAGE_BLANK;
183 writecell(termwriter, cell, *pos);
184 start = 0;
185 continue;
186 }
187
188 if (header) {
189 if (header < 3 && cell.codepoint == '#') {
190 color = nextHeader(color);
191 header++;
192 } else {
193 struct page_cell cell = {0};
194 cell.codepoint = '#';
195 cell.width = 1;
196 cell.color = colorFromLine(color);
197 while (header) {
198 writecell(termwriter, cell, *pos);
199 header--;
200 }
201 }
202 }
203 if (link) {
204 if (cell.codepoint == '>') {
205 uint32_t ch;
206 int n;
207 link = 0;
208 n = gemtext_parse_link(in, pos, length, links,
209 termwriter, &ch);
210 if (n) return n;
211 if (ch == '\n') break;
212 continue;
213 } else {
214 struct page_cell prev_cell = {0};
215 prev_cell.codepoint = '=';
216 prev_cell.width = 1;
217 prev_cell.color = color;
218 writecell(termwriter, prev_cell, *pos);
219 link = 0;
220 }
221 }
222 if (preformat > 0) {
223 if (cell.codepoint == '`') {
224 if (preformat < 2) {
225 preformat++;
226 continue;
227 }
228 *preformatted = !*preformatted;
229 color = colorFromLine(*preformatted ?
230 LINE_PREFORMATTED : LINE_TEXT);
231 preformat = -1;
232 continue;
233 } else {
234 struct page_cell prev_cell = {0};
235 prev_cell.codepoint = '`';
236 prev_cell.width = 1;
237 prev_cell.color = colorFromLine(color);
238 while (preformat) {
239 writecell(termwriter, prev_cell, *pos);
240 preformat--;
241 }
242 }
243 }
244
245 /* start of the line */
246 if (start && (cell.codepoint == '`' || !*preformatted)) {
247 switch (cell.codepoint) {
248 case '=':
249 link = 1;
250 break;
251 case '`':
252 preformat = 1;
253 break;
254 case '#':
255 color = nextHeader(color);
256 header = 1;
257 break;
258 case '>':
259 color = LINE_BLOCKQUOTE;
260 break;
261 case '*':
262 color = LINE_LIST;
263 break;
264 }
265 }
266 start = 0;
267 if (!header && !link && !preformat) {
268 cell.color = colorFromLine(color);
269 writecell(termwriter, cell, *pos);
270 }
271 }
272 *_color = color;
273
274 return ret;
275 }
276
277 int parse_gemtext(int in, size_t length, int width, int out) {
278
279 int color, links, preformatted;
280 size_t pos;
281 uint32_t last;
282 struct termwriter termwriter = {0};
283 if (width < 10) width = 10;
284
285 termwriter.width = width - OFFSETX;
286 termwriter.fd = out;
287 termwriter.pos = 0;
288
289 color = LINE_TEXT;
290 preformatted = links = 0;
291 last = 0;
292 pos = 0;
293 while (pos < length) {
294 int ret;
295 ret = gemtext_parse_line(in, &pos, length, &color,
296 &links, &preformatted, &termwriter, &last);
297 if (ret == -1) return -1;
298 if (ret == PARSE_LINE_SKIP) continue;
299 if (ret == PARSE_LINE_COMPLETED) {
300 color = LINE_TEXT;
301 last = 0;
302 }
303
304 writenewline(&termwriter, pos);
305 }
306
307 {
308 struct page_cell eof = {0};
309 eof.special = PAGE_EOF;
310 writecell(&termwriter, eof, pos);
311 }
312
313 return 0;
314 }
315