diff --git a/config.c b/config.c new file mode 100644 index 0000000..3832ba4 --- /dev/null +++ b/config.c @@ -0,0 +1,208 @@ +#include "lexer.h" +#include "error.h" +#include "preview.h" + +#define CHECK(f, cond) \ + do { \ + int x = (f); \ + if (cond) \ + return x; \ + } while (0) + +#define CHECK_OK(f) CHECK(f, x != STAT_OK) +#define CHECK_NULL(f) CHECK(f, x != STAT_NULL) +#define CHECK_OK_NULL(f) CHECK(f, x != STAT_OK || x != STAT_NULL) + +enum { + STAT_OK, + STAT_ERR, + STAT_NULL, +}; + +static Lexer *lexer; +static Token token; +static VectorPreview *previews; +static char *any_type; + +static void add_preview(char *name, char *script, char *type, char *subtype, + char *ext) +{ + if (type && strcmp(type, any_type) == 0) + type = NULL; + + if (subtype && strcmp(subtype, any_type) == 0) + subtype = NULL; + + Preview p = (Preview){ + .name = name, + .script = script, + .script_len = strlen(script), + .type = type, + .subtype = subtype, + .ext = ext, + .priority = 1 /* custom previews are always prioritized */ + }; + + vectorPreview_append(previews, p); +} + +static inline void next_token(void) +{ + token = lexer_get_token(lexer); +} + +static int accept(enum TokenType type) +{ + if (token.type == type) { + next_token(); + return STAT_OK; + } + + return STAT_NULL; +} + +static int expect(enum TokenType type) +{ + if (accept(type) == STAT_OK) + return STAT_OK; + + if (token.type == TOK_ERR) + return STAT_ERR; + + print_errorf("unexpected token: %s, expected: %s", + lexer_token_type_str(token.type), + lexer_token_type_str(type)); + return STAT_ERR; +} + +static inline char *get_str(Token tok) +{ + return lexer_get_string(lexer, tok); +} + +static int preview_type_ext(char **ext) +{ + CHECK_OK(accept(TOK_DOT)); + + Token tok = token; + CHECK_OK(expect(TOK_STR)); + *ext = get_str(tok); + + return STAT_OK; +} + +static int preview_type_mime_part(char **s) +{ + *s = NULL; + CHECK_NULL(accept(TOK_STAR)); + + Token t = token; + CHECK_OK(expect(TOK_STR)); + *s = get_str(t); + + return STAT_OK; +} + +static int preview_type_mime(char **type, char **subtype) +{ + CHECK_OK(preview_type_mime_part(type)); + CHECK_OK(expect(TOK_SLASH)); + CHECK_OK(preview_type_mime_part(subtype)); + + return STAT_OK; +} + +static int preview_type(char **type, char **subtype, char **ext) +{ + CHECK_NULL(preview_type_ext(ext)); + return preview_type_mime(type, subtype); +} + +static int new_preview(void) +{ + Token name = token; + CHECK_OK(expect(TOK_STR)); + + char *type = NULL; + char *subtype = NULL; + char *ext = NULL; + CHECK_OK(preview_type(&type, &subtype, &ext)); + + CHECK_OK(expect(TOK_BLK_OPEN)); + + Token script = token; + CHECK_OK(expect(TOK_STR)); + + CHECK_OK(expect(TOK_BLK_CLS)); + + add_preview(get_str(name), get_str(script), type, subtype, ext); + return STAT_OK; +} + +static int priority(void) +{ + print_error("priority is not supported yet"); + return STAT_ERR; +} + +static int command(void) +{ + Token cmd = token; + CHECK_OK(expect(TOK_STR)); + + char *cmd_str = get_str(cmd); + if (strcmp(cmd_str, "preview") == 0) + return new_preview(); + else if (strcmp(cmd_str, "priority") == 0) + return priority(); + + print_errorf("unknown command: %s", cmd_str); + return STAT_ERR; +} + +static int commands(void) +{ + accept(TOK_END); + + while (1) { + CHECK_NULL(accept(TOK_EOF)); + CHECK_OK(command()); + CHECK_OK_NULL(accept(TOK_END)); + } +} + +static int parse(void) +{ + next_token(); + + if (commands() == STAT_ERR) + return ERR; + + return OK; +} + +int config_load(VectorPreview *prevs, char *filename, char *any_type_) +{ + int ret = OK; + + FILE *f = fopen(filename, "r"); + ERRCHK_GOTO(!f, ret, exit, FUNCFAILED("fopen"), ERRNOS); + + lexer = lexer_init(f); + previews = prevs; + any_type = any_type_; + + ERRCHK_GOTO_OK(parse(), ret, file); + +file: + fclose(f); + +exit: + return ret; +} + +void config_cleanup(void) +{ + if (lexer) + lexer_free(lexer); +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..0372521 --- /dev/null +++ b/config.h @@ -0,0 +1,9 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "preview.h" + +int config_load(VectorPreview *prevs, char *filename, char *any_type_); +void config_cleanup(void); + +#endif diff --git a/ctpv.c b/ctpv.c index 67b5db1..c908661 100644 --- a/ctpv.c +++ b/ctpv.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -9,13 +10,14 @@ #include "error.h" #include "utils.h" +#include "config.h" #include "server.h" #include "preview.h" #include "previews.h" #define ANY_TYPE "*" -static const char any_type[] = ANY_TYPE; +static char any_type[] = ANY_TYPE; static magic_t magic; @@ -31,11 +33,16 @@ static struct { char *server_id_s; } ctpv = { .mode = MODE_PREVIEW }; +static VectorPreview *previews; + static void cleanup(void) { cleanup_previews(); + config_cleanup(); if (magic != NULL) magic_close(magic); + if (previews) + vectorPreview_free(previews); } static int init_magic(void) @@ -49,9 +56,41 @@ static int init_magic(void) return OK; } -static void init_previews_v(void) +static int create_dir(char *buf, size_t len) { - init_previews(previews, LEN(previews)); + char dir[len]; + strncpy(dir, buf, LEN(dir) - 1); + ERRCHK_RET(mkpath(dir, 0700) == -1, FUNCFAILED("mkpath"), ERRNOS); + + return OK; +} + +static int get_config_file(char *buf, size_t len) +{ + ERRCHK_RET_OK(get_config_dir(buf, len, "ctpv/")); + ERRCHK_RET_OK(create_dir(buf, len)); + + strncat(buf, "config", len - 1); + + if (access(buf, F_OK) != 0) + close(creat(buf, 0600)); + + return OK; +} + +static int init_previews_v(void) +{ + previews = vectorPreview_new(LEN(b_previews)); + vectorPreview_append_arr(previews, b_previews, LEN(b_previews)); + + char config_file[FILENAME_MAX]; + get_config_file(config_file, LEN(config_file)); + + ERRCHK_RET_OK(config_load(previews, config_file, any_type)); + + init_previews(previews->buf, previews->len); + + return OK; } static const char *get_mimetype(const char *path) @@ -133,12 +172,7 @@ static void md5_string(char *buf, size_t len, char *s) static int get_cache_file(char *buf, size_t len, char *file) { ERRCHK_RET_OK(get_cache_dir(buf, len, "ctpv/")); - - { - char dir[len]; - strncpy(dir, buf, LEN(dir) - 1); - ERRCHK_RET(mkpath(dir, 0700) == -1, FUNCFAILED("mkpath"), ERRNOS); - } + ERRCHK_RET_OK(create_dir(buf, len)); char name[64]; md5_string(name, LEN(name) - 1, file); @@ -173,7 +207,7 @@ static int preview(int argc, char *argv[]) ERRCHK_RET_OK(init_magic()); - init_previews_v(); + ERRCHK_RET_OK(init_previews_v()); const char *mimetype; ERRCHK_RET(!(mimetype = get_mimetype(f))); @@ -209,7 +243,7 @@ static int end(void) static int list(void) { - init_previews_v(); + ERRCHK_RET_OK(init_previews_v()); size_t len; Preview p, **list = get_previews_list(&len); diff --git a/lexer.c b/lexer.c index 83ec5dd..708f7b5 100644 --- a/lexer.c +++ b/lexer.c @@ -9,15 +9,17 @@ print_errorf("config parse error:%u:%u " format, (c).line, (c).col \ __VA_OPT__(, ) __VA_ARGS__) -#define TOK_TYPE_ALIAS(t) ((Token){ .type = t }) +#define TOK_TYPE(t) ((Token){ .type = t }) -#define NULL_TOK TOK_TYPE_ALIAS(TOK_NULL) -#define EOF_TOK TOK_TYPE_ALIAS(TOK_EOF) -#define END_TOK TOK_TYPE_ALIAS(TOK_END) -#define ERR_TOK TOK_TYPE_ALIAS(TOK_ERR) +#define NULL_TOK TOK_TYPE(TOK_NULL) +#define EOF_TOK TOK_TYPE(TOK_EOF) +#define END_TOK TOK_TYPE(TOK_END) +#define ERR_TOK TOK_TYPE(TOK_ERR) #define READ_PUNCT(c, t, s) read_punct((c), (t), (s), LEN(s) - 1) +#define EOF_CHAR (-1) + typedef int (*Predicate)(int); typedef struct { @@ -38,7 +40,11 @@ struct Lexer { VectorChar *text_buf; }; -static char block_open[] = "{{{", block_close[] = "}}}"; +static char block_open[] = "{{{", + block_close[] = "}}}", + slash[] = "/", + star[] = "*", + dot[] = "."; static void add_token_queue(Lexer *ctx, Token tok) { @@ -74,7 +80,7 @@ static int peekn_char(Lexer *ctx, unsigned int i) goto exit; if (b->eof || (i > 0 && i >= b->len)) - return -1; + return EOF_CHAR; if (i > 0) { assert(i < LEN(b->buf)); @@ -93,7 +99,7 @@ static int peekn_char(Lexer *ctx, unsigned int i) PRINTINTERR("fread() failed"); if (b->len == 0) - return -1; + return EOF_CHAR; } exit: @@ -217,28 +223,20 @@ static void read_while(Lexer *ctx, Predicate p, int add) add_text_buf(ctx, '\0'); } -static inline Token read_eof(Lexer *ctx) +static inline Token read_end(Lexer *ctx) { - char c = peek_char(ctx); + Token tok = NULL_TOK; - if (c >= 0) - return NULL_TOK; + while (peek_char(ctx) == '\n') { + char c = peek_char(ctx); + if (c != '\n') + break; - next_char(ctx); + next_char(ctx); + tok = END_TOK; + } - return EOF_TOK; -} - -static inline Token read_newline(Lexer *ctx) -{ - char c = peek_char(ctx); - - if (c != '\n') - return NULL_TOK; - - next_char(ctx); - - return END_TOK; + return tok; } static inline Token read_symbol(Lexer *ctx) @@ -274,7 +272,7 @@ static Token read_punct(Lexer *ctx, int type, char *s, int n) { Token tok; - if (peek_char(ctx) < 0) + if (peek_char(ctx) == EOF_CHAR) return EOF_TOK; int ret = cmp_nextn(ctx, n, s); @@ -337,6 +335,15 @@ static Token read_block(Lexer *ctx) return t; \ } while (0) +#define ATTEMPT_READ_CHAR(ctx, ch, type) \ + do { \ + char c = peek_char(ctx); \ + if (c == (ch)) { \ + next_char(ctx); \ + return (type); \ + } \ + } while (0) + Token lexer_get_token(Lexer *ctx) { if (!is_empty_token_queue(ctx)) @@ -344,8 +351,12 @@ Token lexer_get_token(Lexer *ctx) read_while(ctx, isblank, 0); - ATTEMPT_READ(ctx, read_eof); - ATTEMPT_READ(ctx, read_newline); + ATTEMPT_READ_CHAR(ctx, EOF_CHAR, EOF_TOK); + ATTEMPT_READ_CHAR(ctx, '/', TOK_TYPE(TOK_SLASH)); + ATTEMPT_READ_CHAR(ctx, '*', TOK_TYPE(TOK_STAR)); + ATTEMPT_READ_CHAR(ctx, '.', TOK_TYPE(TOK_DOT)); + + ATTEMPT_READ(ctx, read_end); ATTEMPT_READ(ctx, read_symbol); ATTEMPT_READ(ctx, read_digit); ATTEMPT_READ(ctx, read_block); @@ -361,3 +372,34 @@ char *lexer_get_string(Lexer *ctx, Token tok) return get_text_buf_at(ctx, tok.val.sp); } + +char *lexer_token_type_str(enum TokenType type) +{ + switch (type) { + case TOK_NULL: + return ""; + case TOK_EOF: + return ""; + case TOK_ERR: + return ""; + case TOK_END: + return ""; + case TOK_BLK_OPEN: + return block_open; + case TOK_BLK_CLS: + return block_close; + case TOK_SLASH: + return slash; + case TOK_STAR: + return star; + case TOK_DOT: + return dot; + case TOK_INT: + return ""; + case TOK_STR: + return ""; + } + + PRINTINTERR("unknown type: %d", type); + abort(); +} diff --git a/lexer.h b/lexer.h index 9a107a0..d830d20 100644 --- a/lexer.h +++ b/lexer.h @@ -7,13 +7,16 @@ typedef struct Lexer Lexer; typedef struct { - enum { + enum TokenType { TOK_NULL, TOK_EOF, TOK_ERR, TOK_END, TOK_BLK_OPEN, TOK_BLK_CLS, + TOK_SLASH, + TOK_STAR, + TOK_DOT, TOK_INT, TOK_STR, } type; @@ -27,5 +30,6 @@ Lexer *lexer_init(FILE *f); void lexer_free(Lexer *ctx); Token lexer_get_token(Lexer *ctx); char *lexer_get_string(Lexer *ctx, Token tok); +char *lexer_token_type_str(enum TokenType type); #endif diff --git a/preview.c b/preview.c index 1d3441a..33e42e9 100644 --- a/preview.c +++ b/preview.c @@ -11,6 +11,8 @@ #define PREVP_SIZE sizeof(Preview *) +VECTOR_GEN_SOURCE(Preview, Preview) + static struct { size_t len; Preview **list; diff --git a/preview.h b/preview.h index d2ac83d..d388250 100644 --- a/preview.h +++ b/preview.h @@ -3,12 +3,16 @@ #include +#include "vector.h" + typedef struct { char *name, *ext, *type, *subtype, *script; int priority; size_t script_len; } Preview; +VECTOR_GEN_HEADER(Preview, Preview) + typedef struct { char *f, *w, *h, *x, *y, *id; char *cache_file; diff --git a/previews.h b/previews.h index 35236d3..f7cf436 100644 --- a/previews.h +++ b/previews.h @@ -13,7 +13,7 @@ #define PP(e, t, s, n, p) { #n, e, t, s, PNAME(n), p, LEN(PNAME(n)) } #define PR(e, t, s, n) PP(e, t, s, n, 0) -Preview previews[] = { +Preview b_previews[] = { PP(NULL, NULL, NULL, wrapper, INT_MAX), PR(NULL, "text", NULL, bat), PR(NULL, "text", NULL, highlight), diff --git a/utils.c b/utils.c index 7065a3c..2f22f26 100644 --- a/utils.c +++ b/utils.c @@ -87,22 +87,32 @@ int strlennull(const char *s) return s ? strlen(s) : 0; } -int get_cache_dir(char *buf, size_t len, char *name) +static int get_xdg_dir(char *buf, size_t len, char *var, char *var_sub, char *name) { - char *home, *cache_d, cache_d_buf[FILENAME_MAX]; + char *home, *dir, dir_buf[FILENAME_MAX]; - if (!(cache_d = getenv("XDG_CACHE_HOME"))) { + if (!(dir = getenv(var))) { home = getenv("HOME"); ERRCHK_RET(!home, "HOME env var does not exist"); - snprintf(cache_d_buf, LEN(cache_d_buf)-1, "%s/.cache", home); - cache_d = cache_d_buf; + snprintf(dir_buf, LEN(dir_buf)-1, "%s/%s", home, var_sub); + dir = dir_buf; } - snprintf(buf, len - 1, "%s/%s", cache_d, name); + snprintf(buf, len - 1, "%s/%s", dir, name); return OK; } +int get_cache_dir(char *buf, size_t len, char *name) +{ + return get_xdg_dir(buf, len, "XDG_CACHE_HOME", ".cache", name); +} + +int get_config_dir(char *buf, size_t len, char *name) +{ + return get_xdg_dir(buf, len, "XDG_CONFIG_HOME", ".config", name); +} + int mkpath(char* file_path, mode_t mode) { for (char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) { diff --git a/utils.h b/utils.h index 63468cf..31091a4 100644 --- a/utils.h +++ b/utils.h @@ -32,6 +32,7 @@ int strcmpnull(const char *s1, const char *s2); int strlennull(const char *s); int get_cache_dir(char *buf, size_t len, char *name); +int get_config_dir(char *buf, size_t len, char *name); int mkpath(char* file_path, mode_t mode);