Add custom previews support

This commit is contained in:
Nikita Ivanov 2022-06-08 04:27:30 +05:00
parent 55cd11f545
commit ab47acdf04
No known key found for this signature in database
GPG Key ID: 6E656AC5B97B5133
10 changed files with 362 additions and 48 deletions

208
config.c Normal file
View File

@ -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);
}

9
config.h Normal file
View File

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

56
ctpv.c
View File

@ -1,5 +1,6 @@
#include <stdio.h>
#include <magic.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
@ -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);

96
lexer.c
View File

@ -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);
return EOF_TOK;
tok = END_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 "<null>";
case TOK_EOF:
return "<end of file>";
case TOK_ERR:
return "<TOKEN ERROR>";
case TOK_END:
return "<end>";
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 "<integer>";
case TOK_STR:
return "<string>";
}
PRINTINTERR("unknown type: %d", type);
abort();
}

View File

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

View File

@ -11,6 +11,8 @@
#define PREVP_SIZE sizeof(Preview *)
VECTOR_GEN_SOURCE(Preview, Preview)
static struct {
size_t len;
Preview **list;

View File

@ -3,12 +3,16 @@
#include <stdlib.h>
#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;

View File

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

22
utils.c
View File

@ -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, '/')) {

View File

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