💾 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

View Raw

More Information

-=-=-=-=-=-=-

Go Back

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