💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Vgmi › files › f8788591f09965601765a24772ef2… captured on 2024-02-05 at 10:00:53. 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, length))
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, length))
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, length)) 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, length)) 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 #define PARSE_LINE_IGNORE 3
150 int gemtext_parse_line(int in, size_t *pos, size_t length, int *_color,
151 int *links, int *preformatted,
152 struct termwriter *termwriter, uint32_t *last) {
153
154 int ret;
155 int color = *_color, header = 0, link = 0, preformat = 0, start = 1;
156
157 if (*preformatted) color = colorFromLine(LINE_PREFORMATTED);
158
159 ret = PARSE_LINE_COMPLETED;
160 while (*pos < length) {
161
162 struct page_cell cell = {0};
163
164 if (*last) {
165 cell.codepoint = *last;
166 *last = 0;
167 } else if (readnext(in, &cell.codepoint, pos, length)) {
168 return -1;
169 }
170 if (!renderable(cell.codepoint)) continue;
171 if (preformat == -1) {
172 if (cell.codepoint == '\n') {
173 ret = PARSE_LINE_IGNORE;
174 *last = 0;
175 break;
176 }
177 preformat = 0;
178 }
179 if (cell.codepoint == '\n') break;
180 cell.width = mk_wcwidth(cell.codepoint);
181 if (cell.width >= termwriter->width) cell.width = 1;
182
183 if (header) {
184 if (header < 3 && cell.codepoint == '#') {
185 color = nextHeader(color);
186 header++;
187 } else {
188 struct page_cell cell = {0};
189 cell.codepoint = '#';
190 cell.width = 1;
191 cell.color = colorFromLine(color);
192 while (header) {
193 writecell(termwriter, cell, *pos);
194 header--;
195 }
196 }
197 }
198 if (link) {
199 if (cell.codepoint == '>') {
200 uint32_t ch;
201 int n;
202 link = 0;
203 n = gemtext_parse_link(in, pos, length, links,
204 termwriter, &ch);
205 if (n) return n;
206 if (ch == '\n') break;
207 continue;
208 } else {
209 struct page_cell prev_cell = {0};
210 prev_cell.codepoint = '=';
211 prev_cell.width = 1;
212 prev_cell.color = color;
213 writecell(termwriter, prev_cell, *pos);
214 link = 0;
215 }
216 }
217 if (preformat > 0) {
218 if (cell.codepoint == '`') {
219 if (preformat < 2) {
220 preformat++;
221 continue;
222 }
223 *preformatted = !*preformatted;
224 color = colorFromLine(*preformatted ?
225 LINE_PREFORMATTED : LINE_TEXT);
226 preformat = -1;
227 continue;
228 } else {
229 struct page_cell prev_cell = {0};
230 prev_cell.codepoint = '`';
231 prev_cell.width = 1;
232 prev_cell.color = colorFromLine(color);
233 while (preformat) {
234 writecell(termwriter, prev_cell, *pos);
235 preformat--;
236 }
237 }
238 }
239
240 /* start of the line */
241 if (start && (cell.codepoint == '`' || !*preformatted)) {
242 switch (cell.codepoint) {
243 case '=':
244 link = 1;
245 break;
246 case '`':
247 preformat = 1;
248 break;
249 case '#':
250 color = nextHeader(color);
251 header = 1;
252 break;
253 case '>':
254 color = LINE_BLOCKQUOTE;
255 break;
256 case '*':
257 color = LINE_LIST;
258 break;
259 }
260 }
261 start = 0;
262 if (!header && !link && !preformat) {
263 cell.color = colorFromLine(color);
264 writecell(termwriter, cell, *pos);
265 }
266 }
267 *_color = color;
268
269 return ret;
270 }
271
272 int parse_gemtext(int in, size_t length, int width, int out) {
273
274 int color, links, preformatted;
275 size_t pos;
276 uint32_t last;
277 struct termwriter termwriter = {0};
278 if (width < 10) width = 10;
279
280 termwriter.width = width - OFFSETX;
281 termwriter.fd = out;
282 termwriter.pos = 0;
283
284 color = LINE_TEXT;
285 preformatted = links = 0;
286 last = 0;
287 pos = 0;
288 while (pos < length) {
289 int ret;
290 ret = gemtext_parse_line(in, &pos, length, &color,
291 &links, &preformatted, &termwriter, &last);
292 if (ret == -1) return -1;
293 if (ret == PARSE_LINE_SKIP) continue;
294 if (ret == PARSE_LINE_COMPLETED || ret == PARSE_LINE_IGNORE) {
295 color = LINE_TEXT;
296 last = 0;
297 }
298
299 if (ret != PARSE_LINE_IGNORE)
300 writenewline(&termwriter, pos);
301 }
302
303 {
304 struct page_cell eof = {0};
305 eof.special = PAGE_EOF;
306 writecell(&termwriter, eof, pos);
307 }
308
309 return 0;
310 }
311