💾 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

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))

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