#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "gemtext.h"
static struct gemtext *
_case_default(char **text)
{
struct gemtext_text *ret;
char *ptr;
char *cpy;
ptr = *text;
cpy = ptr;
ret = malloc(sizeof(*ret));
if (ret == NULL)
return NULL;
memset(ret, 0, sizeof(*ret));
ret->type = GEMTEXT_TEXT;
while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n')
ptr++;
ret->text = strndup(cpy, ptr-cpy);
if (ret->text == NULL) {
gemtext_free((struct gemtext *)ret);
return NULL;
}
if (*ptr == '\r') {
if (strncmp(ptr, "\r\n", 2) == 0)
ptr += 2;
else
ptr++;
} else if (*ptr == '\n') {
ptr++;
}
*text = ptr;
return (struct gemtext *)ret;
}
static struct gemtext *
_case_link(char **text)
{
struct gemtext_link *ret;
char *ptr;
char *cpy;
ptr = *text;
if (*(ptr+1) != '>')
return _case_default(text);
ret = malloc(sizeof(*ret));
if (ret == NULL)
return NULL;
memset(ret, 0, sizeof(*ret));
ret->type = GEMTEXT_LINK;
ptr += 2; /* skip "=>" */
while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t'))
ptr++;
cpy = ptr;
while (*ptr != '\r' && *ptr != '\n' && *ptr != ' ' && *ptr != '\t' && *ptr != '\0')
ptr++;
ret->link = strndup(cpy, ptr-cpy);
if (*ptr == ' ' || *ptr == '\t') {
while(*ptr == ' ' || *ptr == '\t')
ptr++;
cpy = ptr;
while (*ptr != '\r' && *ptr != '\n' && *ptr != '\0')
ptr++;
ret->name = strndup(cpy, ptr-cpy);
}
if (*ptr == '\r') {
if (strncmp(ptr, "\r\n", 2) == 0)
ptr += 2;
else
ptr++;
} else if (*ptr == '\n') {
ptr++;
}
*text = ptr;
return (struct gemtext *)ret;
}
static struct gemtext *
_case_h1(char **text)
{
struct gemtext_h1 *ret;
char *ptr;
char *cpy;
ptr = *text;
ret = malloc(sizeof(*ret));
if (ret == NULL)
return NULL;
memset(ret, 0, sizeof(*ret));
ret->type = GEMTEXT_H1;
ptr++;
while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t'))
ptr++;
cpy = ptr;
while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n')
ptr++;
ret->text = strndup(cpy, ptr-cpy);
if (ret->text == NULL) {
gemtext_free((struct gemtext *)ret);
return NULL;
}
if (*ptr == '\r') {
if (strncmp(ptr, "\r\n", 2) == 0)
ptr += 2;
else
ptr++;
} else if (*ptr == '\n') {
ptr++;
}
*text = ptr;
return (struct gemtext *)ret;
}
static struct gemtext *
_case_h2(char **text)
{
struct gemtext_h2 *ret;
char *ptr;
char *cpy;
ptr = *text;
ret = malloc(sizeof(*ret));
if (ret == NULL)
return NULL;
memset(ret, 0, sizeof(*ret));
ret->type = GEMTEXT_H2;
ptr += 2;
while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t'))
ptr++;
cpy = ptr;
while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n')
ptr++;
ret->text = strndup(cpy, ptr-cpy);
if (ret->text == NULL) {
gemtext_free((struct gemtext *)ret);
return NULL;
}
if (*ptr == '\r') {
if (strncmp(ptr, "\r\n", 2) == 0)
ptr += 2;
else
ptr++;
} else if (*ptr == '\n') {
ptr++;
}
*text = ptr;
return (struct gemtext *)ret;
}
static struct gemtext *
_case_h3(char **text)
{
struct gemtext_h3 *ret;
char *ptr;
char *cpy;
ptr = *text;
ret = malloc(sizeof(*ret));
if (ret == NULL)
return NULL;
memset(ret, 0, sizeof(*ret));
ret->type = GEMTEXT_H3;
ptr += 3;
while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t'))
ptr++;
cpy = ptr;
while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n')
ptr++;
ret->text = strndup(cpy, ptr-cpy);
if (ret->text == NULL) {
gemtext_free((struct gemtext *)ret);
return NULL;
}
if (*ptr == '\r') {
if (strncmp(ptr, "\r\n", 2) == 0)
ptr += 2;
else
ptr++;
} else if (*ptr == '\n') {
ptr++;
}
*text = ptr;
return (struct gemtext *)ret;
}
static struct gemtext *
_case_h(char **text)
{
char *ptr;
ptr = *text;
if (*(ptr+1) == '#') {
if (*(ptr+2) == '#')
return _case_h3(text);
else
return _case_h2(text);
} else {
return _case_h1(text);
}
/* NOT REACHED */
return NULL;
}
static struct gemtext *
_case_pre(char **text)
{
struct gemtext_pre *ret;
char *ptr;
char *cpy;
ptr = *text;
if (strncmp(ptr, "```", 3) != 0)
return _case_default(text);
ret = malloc(sizeof(*ret));
if (ret == NULL)
return NULL;
memset(ret, 0, sizeof(*ret));
ret->type = GEMTEXT_PRE;
ptr += 3;
if (*ptr == '\r') {
if (strncmp(ptr, "\r\n", 2) == 0)
ptr += 2;
else
ptr++;
} else if (*ptr == '\n') {
ptr++;
}
cpy = ptr;
while (*ptr != '\0') {
if (*ptr == '\n' || *ptr == '\r') {
if (strncmp(ptr+1, "```", 3) == 0) {
ptr++;
ret->text = strndup(cpy, ptr-cpy);
if (ret->text == NULL) {
gemtext_free((struct gemtext *)ret);
return NULL;
}
break;
}
}
ptr++;
}
/* true if closing "```" was never found
in while loop above */
if (*ptr == '\0') {
ret->text = strndup(cpy, ptr-cpy);
if (ret->text == NULL) {
gemtext_free((struct gemtext *)ret);
return NULL;
}
}
/* should be always true if above statement is false */
if (*ptr == '`') {
while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n')
ptr++;
}
if (*ptr == '\r') {
if (strncmp(ptr, "\r\n", 2) == 0)
ptr += 2;
else
ptr++;
} else if (*ptr == '\n') {
ptr++;
}
*text = ptr;
return (struct gemtext *)ret;
}
static struct gemtext *
_case_ul(char **text)
{
struct gemtext_ul *ret;
char *ptr;
char *cpy;
ptr = *text;
ret = malloc(sizeof(*ret));
if (ret == NULL)
return NULL;
memset(ret, 0, sizeof(*ret));
ret->type = GEMTEXT_UL;
if (*(ptr+1) != ' ')
return _case_default(text);
ptr += 2; /* skip whitespace after '*' */
cpy = ptr;
while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n')
ptr++;
ret->text = strndup(cpy, ptr-cpy);
if (ret->text == NULL) {
gemtext_free((struct gemtext *)ret);
return NULL;
}
if (*ptr == '\r') {
if (strncmp(ptr, "\r\n", 2) == 0)
ptr += 2;
else
ptr++;
} else if (*ptr == '\n') {
ptr++;
}
*text = ptr;
return (struct gemtext *)ret;
}
static struct gemtext *
_case_qt(char **text)
{
struct gemtext_qt *ret;
char *ptr;
char *cpy;
ptr = *text;
ret = malloc(sizeof(*ret));
if (ret == NULL)
return NULL;
memset(ret, 0, sizeof(*ret));
ret->type = GEMTEXT_QT;
ptr++;
cpy = ptr;
while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n')
ptr++;
ret->text = strndup(cpy, ptr-cpy);
if (ret->text == NULL) {
gemtext_free((struct gemtext *)ret);
return NULL;
}
if (*ptr == '\r') {
if (strncmp(ptr, "\r\n", 2) == 0)
ptr += 2;
else
ptr++;
} else if (*ptr == '\n') {
ptr++;
}
*text = ptr;
return (struct gemtext *)ret;
}
static struct gemtext *
_gemtext_decode(char **text)
{
switch (**text) {
case '=':
return _case_link(text);
case '#':
return _case_h(text);
case '`':
return _case_pre(text);
case '*':
return _case_ul(text);
case '>':
return _case_qt(text);
default:
return _case_default(text);
}
/* NOT REACHED */
return NULL;
}
struct gemtext *
gemtext_decode(char *text)
{
return _gemtext_decode(&text);
}
struct gemtext *
gemtext_decode_fd(int fd)
{
struct gemtext *ret;
struct stat fst;
char *tbuf;
if (fstat(fd, &fst) == -1)
return NULL;
tbuf = malloc(fst.st_size+1);
if (tbuf == NULL)
return NULL;
memset(tbuf, 0, fst.st_size+1);
if (read(fd, tbuf, fst.st_size) == -1) {
free(tbuf);
return NULL;
}
ret = gemtext_decode(tbuf);
free(tbuf);
if (ret == NULL)
return NULL;
return ret;
}
struct gemtext *
gemtext_decode_file(const char *path)
{
struct gemtext *ret;
int fd;
fd = open(path, O_RDONLY);
if (fd == -1)
return NULL;
ret = gemtext_decode_fd(fd);
close(fd);
if (ret == NULL)
return NULL;
return ret;
}
struct gemtext **
gemtext_list_decode(char *text)
{
struct gemtext **ret;
struct gemtext **append;
struct gemtext *item;
char *ptr;
ret = NULL;
ptr = text;
while (*ptr) {
item = _gemtext_decode(&ptr);
if (item == NULL) {
gemtext_list_free(ret);
return NULL;
}
append = gemtext_list_append(ret, item);
if (append == NULL) {
gemtext_list_free(ret);
return NULL;
}
ret = append;
}
/*
* if this is reached, it means there was no text to parse
* therefore NULL would be returned, but we don't want that
* because it is the error indicator, so we'll create an
* empty list
*/
if (ret == NULL) {
ret = malloc(sizeof(*ret));
if (ret == NULL)
return NULL;
ret[0] = NULL;
}
return ret;
}
struct gemtext **
gemtext_list_decode_fd(int fd)
{
struct gemtext **ret;
struct stat fst;
char *tbuf;
if (fstat(fd, &fst) == -1)
return NULL;
tbuf = malloc(fst.st_size+1);
if (tbuf == NULL)
return NULL;
memset(tbuf, 0, fst.st_size+1);
if (read(fd, tbuf, fst.st_size) == -1) {
free(tbuf);
return NULL;
}
ret = gemtext_list_decode(tbuf);
free(tbuf);
if (ret == NULL)
return NULL;
return ret;
}
struct gemtext **
gemtext_list_decode_file(const char *path)
{
struct gemtext **ret;
int fd;
fd = open(path, O_RDONLY);
if (fd == -1)
return NULL;
ret = gemtext_list_decode_fd(fd);
close(fd);
if (ret == NULL)
return NULL;
return ret;
}