initial commit

This commit is contained in:
2025-08-31 07:34:13 -04:00
commit be5007d57c
23 changed files with 1013 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
compile_commands.json
.cache

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "lib/log.c"]
path = lib/log.c
url = https://github.com/rxi/log.c
[submodule "lib/ds"]
path = lib/ds
url = https://git.squi.bid/squibid/ds

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# Womblic
command line helper tool which does everything for me
usage:
```bash
wom
```
# TODO
add dev env creator

22
completions/_wom.zsh Normal file
View File

@@ -0,0 +1,22 @@
#compdef wom
_arguments \
'1:flag:->flags' \
'*:: :->args'
case "$state" in
flags)
local -a opts
opts=(
'-c:Path to config file'
'-v:Show version and exit'
'-h:Show help text'
$(wom subcmds)
)
_describe 'flags' opts
;;
args)
case $line[1] in
-c) _files ;;
esac
esac

47
include/api.h Normal file
View File

@@ -0,0 +1,47 @@
#pragma once
#include "ds.h"
typedef struct {
char *name;
void (*cb)(void *, int argc, char *argv[]);
void *data;
} wom_subcmd_t;
/**
* @brief pointer to all registered subcommands
*/
extern ds_sll_t *subcmds;
/**
* @brief register a new subcommand
*
* @param name name which should be used to call subcommand
* @param cb function to run when name is called
* @param data pointer to data to pass to the cb
* @return 0 on success
*/
int register_subcmd(char *name, void (*cb)(void *, int argc, char *argv[]), void *data);
/**
* @brief list out all registered subcmds
*
* @return list of subcmds
*/
char **list_subcmds();
/**
* @brief run subcmds mentioned in argv
*
* @param argc argc
* @param argv argv
*/
void run_subcmds(int argc, char *argv[]);
/**
* @brief unregister and free all subcmds
*
* @return 0 on success
*/
int cleanup_subcmds();

15
include/conf.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
/**
* @brief attempt to find the config path
*
* @return the path
*/
char *conf_config_path(void);
/**
* @brief attempt to find the state path
*
* @return the path
*/
char *conf_state_path(void);

20
include/lua/wom.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
/**
* @brief list of all functions
*/
extern struct luaL_Reg *womlib;
int wom_register_subcmd(lua_State *L);
/**
* @brief push a lua table with all the subcmds onto the lua stack
*
* @param L the lua state
* @return
*/
int wom_list_subcmds(lua_State *L);

26
include/lua/wom_fs.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
/**
* @brief list of all filesystem functions
*/
extern struct luaL_Reg *womfslib;
/**
* @brief create a fs iter for lua
*
* @param L the lua state
* @return
*/
int wom_fs_dir(lua_State *L);
/**
* @brief get the type of a directory in lua
*
* @param L the lua state
* @return
*/
int wom_fs_type(lua_State *L);

11
include/subcmds.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
/**
* The funcs below are not in need of documentation because they follow the
* subcmd pattern
*/
void timer_subcmd(void *, int argc, char *argv[]);
void subcmds_dev(void *, int argc, char *argv[]);
void motd_subcmd(void *, int argc, char *argv[]);
void subcmds_subcmd(void *, int argc, char *argv[]);

1
lib/ds Submodule

Submodule lib/ds added at 16470f2592

1
lib/log.c Submodule

Submodule lib/log.c added at f9ea34994b

51
meson.build Normal file
View File

@@ -0,0 +1,51 @@
project('womblic', 'c',
version: '0.0.1',
license: 'GPLv3')
add_project_arguments([
'-DVERSION="@0@"'.format(meson.project_version()),
'-DLOG_USE_COLOR' # enable colored logs
], language: 'c')
# get all the source files for the executable
luafiles = files(
'src/lua/wom.c',
'src/lua/wom_fs.c'
)
subcmds = files(
'src/subcmds/clock.c',
'src/subcmds/dev.c',
'src/subcmds/motd.c',
'src/subcmds/project.c',
'src/subcmds/subcmds.c',
)
srcfiles = files(
'src/main.c',
'src/api.c',
'src/conf.c'
) + luafiles + subcmds
# build the executable
executable('wom', srcfiles,
dependencies: [
dependency('lua', version: '>=5.1 <6.0'),
],
include_directories: [
include_directories('include'),
include_directories('lib/log.c/src'),
include_directories('lib/ds'),
],
link_with: [
static_library('ds', 'lib/ds/ds.c',
include_directories: 'lib/ds'),
static_library('log.c', 'lib/log.c/src/log.c',
include_directories: 'lib/log.c/src'),
]
)
install_data('completions/_wom.zsh',
install_dir: '/usr/local/share/zsh/site-functions/'
)

120
src/api.c Normal file
View File

@@ -0,0 +1,120 @@
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "ds.h"
#include "log.h"
#include "api.h"
ds_sll_t *subcmds;
int
register_subcmd(char *name, void (*cb)(void *, int argc, char *argv[]), void *data)
{
char *c;
ds_sll_t *newnode = calloc(1, sizeof(ds_sll_t));
wom_subcmd_t *newsubcmd = calloc(1, sizeof(wom_subcmd_t));
/* make sure the subcmd name doesn't contain a space */
for (c = name; *c != '\0'; c++) {
if (isspace(*c)) {
log_fatal("subcmd '%s' can't contain any whitespace characters", name);
return 1;
}
}
/* setup data */
newsubcmd->name = name;
newsubcmd->cb = cb;
newsubcmd->data = data;
newnode->data = newsubcmd;
newnode->next = NULL;
/* if this is the first node */
if (!subcmds) {
subcmds = newnode;
return 0;
}
/* otherwise append the node to the end of the current nodes */
ds_ll_foreach(ds_sll_t, subcmds) {
if (!cur->next) {
cur->next = newnode;
break;
}
}
return 0;
}
char
**list_subcmds(void)
{
int i;
char **subcmdp;
ds_sll_t *node;
wom_subcmd_t *subcmd;
/* get the number of elements in the linked list */
/* TEST: for some reason we need to allocate an extra space in the array or
* else */
for (i = 1, node = subcmds; node; node = node->next, i++)
;
subcmdp = calloc(i, sizeof(char *));
for (i = 0, node = subcmds; node; node = node->next, i++) {
subcmd = node->data;
if (subcmd->name) {
subcmdp[i] = subcmd->name;
}
}
return subcmdp;
}
void
run_subcmds(int argc, char *argv[])
{
int i, j;
char **args;
wom_subcmd_t *subcmd;
/* check for subcommands to run */
for (i = 1; i < argc; i++) {
ds_ll_foreach(ds_sll_t, subcmds) {
subcmd = cur->data;
if (strcmp(argv[i], subcmd->name) == 0) {
goto args;
}
}
}
return;
args:
args = calloc(argc - i, sizeof(char *));
for (j = 0; i < argc; i++, j++) {
args[j] = argv[i];
}
subcmd->cb(subcmd->data, j, args);
free(args);
}
int
cleanup_subcmds(void)
{
ds_sll_t *prevnode, *node;
for (node = subcmds; node;) {
free(node->data);
prevnode = node;
node = node->next;
free(prevnode);
}
free(node);
return 0;
}

54
src/conf.c Normal file
View File

@@ -0,0 +1,54 @@
#include <stdlib.h>
#include <string.h>
#include "conf.h"
char *_conf_state_path = NULL;
char
*conf_config_path(void)
{
char *path, *config_path;
config_path = NULL;
if ((path = getenv("XDG_CONFIG_HOME"))) {
config_path = calloc(
strlen(path) + strlen("/wom/init.lua") + 1,
sizeof(char)
);
config_path = strcat(path, "/wom/init.lua");
} else if ((path = getenv("HOME"))) {
config_path = calloc(
strlen(path) + strlen("/.config/wom/init.lua") + 1,
sizeof(char)
);
config_path = strcat(path, "/.config/wom/init.lua");
}
return config_path;
}
char
*conf_state_path(void)
{
char *path, *state_path;
if (_conf_state_path) {
return _conf_state_path;
}
state_path = NULL;
if ((path = getenv("XDG_DATA_HOME"))) {
state_path = calloc(strlen(path), sizeof(char));
state_path = strcat(path, "/wom/");
} else if ((path = getenv("HOME"))) {
state_path = calloc(strlen(path), sizeof(char));
state_path = strcat(path, "/.local/share/wom/");
}
_conf_state_path = state_path;
return _conf_state_path;
}

87
src/lua/wom.c Normal file
View File

@@ -0,0 +1,87 @@
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <stdlib.h>
#include "lua/wom.h"
#include "api.h"
struct luaL_Reg *womlib = (luaL_Reg[]){
{ .name = "register_subcmd", .func = wom_register_subcmd },
{ .name = "list_subcmds", .func = wom_list_subcmds },
{ NULL, NULL }
};
static lua_State *l;
/**
* @brief wrap a lua function in C
*/
static inline void
wrapper_func(void *data, int argc, char *argv[])
{
int i;
lua_rawgeti(l, LUA_REGISTRYINDEX, *(int *)data);
lua_pushvalue(l, 1);
/* populate lua table with argv */
lua_createtable(l, 0, argc);
for (i = 0; i < argc; i++) {
lua_pushstring(l, argv[i]);
lua_rawseti(l, -2, i + 1);
}
lua_call(l, 1, 0);
l = NULL;
return;
}
int
wom_register_subcmd(lua_State *L)
{
char *name;
int ret, *cb = calloc(1, sizeof(int));
luaL_checktype(L, 1, LUA_TSTRING);
name = (char *)luaL_checkstring(L, 1);
luaL_checktype(L, 2, LUA_TFUNCTION);
/* store the function */
*cb = luaL_ref(L, LUA_REGISTRYINDEX);
l = L;
ret = register_subcmd(name, wrapper_func, cb);
if (ret != 0) {
lua_error(L);
}
lua_pushinteger(L, ret);
return 1;
}
int
wom_list_subcmds(lua_State *L)
{
int i;
char **p = list_subcmds();
/* get the number of elements in the array */
for (i = 0; p[i]; i++)
;
lua_createtable(L, i, 0);
/* fill up the lua table from the c array */
for (i = 0; p[i]; i++) {
lua_pushstring(L, p[i]);
lua_rawseti(L, -2, i + 1);
}
free(p);
return 1;
}

96
src/lua/wom_fs.c Normal file
View File

@@ -0,0 +1,96 @@
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include "lua/wom_fs.h"
struct luaL_Reg *womfslib = (luaL_Reg[]){
{ .name = "dir", .func = wom_fs_dir },
{ .name = "type", .func = wom_fs_type },
{ NULL, NULL }
};
static int
dir_gc(lua_State *L)
{
DIR *d = *(DIR **)lua_touserdata(L, 1);
if (d) {
closedir(d);
}
return 0;
}
static int
dir_iter(lua_State *L)
{
DIR *d = *(DIR **)lua_touserdata(L, lua_upvalueindex(1));
struct dirent *entry;
if ((entry = readdir(d)) != NULL) {
lua_pushstring(L, entry->d_name);
return 1;
}
return 0; /* no more values to return */
}
int
wom_fs_dir(lua_State *L)
{
const char *path;
DIR **d;
path = luaL_checkstring(L, 1);
/* create directory pointer in the user data */
d = lua_newuserdata(L, sizeof(DIR *));
/* set its metatable */
lua_newtable(L);
lua_pushcfunction(L, dir_gc);
lua_setfield(L, -2, "__gc");
lua_setmetatable(L, -2);
/* try to open the specified directory */
if ((*d = opendir(path)) == NULL) {
luaL_error(L, "cannot open %s: %s", path, strerror(errno));
}
/* creates and returns the iterator function with the data *DIR attached */
lua_pushcclosure(L, dir_iter, 1);
return 1;
}
int
wom_fs_type(lua_State *L)
{
const char *path;
struct stat s;
path = luaL_checkstring(L, 1);
if (stat(path, &s) != 0) {
luaL_error(L, "cannot open '%s': %s", path, strerror(errno));
}
switch (s.st_mode & 0170000) {
case __S_IFDIR: lua_pushstring(L, "directory"); break;
case __S_IFCHR: lua_pushstring(L, "chardevice"); break;
case __S_IFBLK: lua_pushstring(L, "blockdevice"); break;
case __S_IFREG: lua_pushstring(L, "file"); break;
case __S_IFIFO: lua_pushstring(L, "fifo"); break;
case __S_IFLNK: lua_pushstring(L, "symlink"); break;
case __S_IFSOCK: lua_pushstring(L, "socket"); break;
default: luaL_error(L, "not able to determine type of %s", path); break;
}
return 1;
}

95
src/main.c Normal file
View File

@@ -0,0 +1,95 @@
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "conf.h"
#include "lua/wom.h"
#include "lua/wom_fs.h"
#include "subcmds.h"
#include "api.h"
static lua_State
*load_wom_lua_lib(lua_State *L)
{
/* open up default libraries */
luaL_openlibs(L);
/* load in the wom library */
lua_newtable(L);
/* load in version info */
lua_pushstring(L, "version");
lua_pushstring(L, VERSION);
lua_settable(L, -3);
/* load in the filesystem library */
lua_pushstring(L, "fs");
lua_createtable(L, 0, 0);
luaL_setfuncs(L, womfslib, 0);
lua_settable(L, -3);
/* load in all the clua functions */
luaL_setfuncs(L, womlib, 0);
lua_setglobal(L, "wom");
return L;
}
int
main(int argc, char *argv[])
{
int c, l;
/* FIXME: config path can't be freed after being passed to lualib which causes
* a minor memory leak */
char *config_path = { 0 };
lua_State *L;
/* general options for womblic */
while ((c = getopt(argc, argv, "hvc:")) != -1) {
switch (c) {
case 'c':
l = strlen(optarg);
config_path = calloc(l + 1, sizeof(char));
strncpy(config_path, optarg, l);
break;
case 'v': printf("%s-%s\n", argv[0], VERSION); break;
case 'h':
default:
printf("help text\n");
break;
}
}
/* setup for subcommands */
register_subcmd("motd", motd_subcmd, NULL);
register_subcmd("timer", timer_subcmd, NULL);
register_subcmd("subcmds", subcmds_subcmd, NULL);
register_subcmd("dev", subcmds_dev, NULL);
/* if the user didn't specify a config path */
if (!config_path) {
config_path = conf_config_path();
}
/* load lua library */
L = luaL_newstate();
L = load_wom_lua_lib(L);
/* run lua code */
if (luaL_loadfile(L, config_path) || lua_pcall(L, 0, 0, 0)) {
lua_error(L);
} else {
lua_pop(L, lua_gettop(L));
}
run_subcmds(argc, argv);
lua_close(L);
cleanup_subcmds();
return 0;
}

98
src/subcmds/clock.c Normal file
View File

@@ -0,0 +1,98 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "subcmds.h"
/**
* @brief convert a sting to a time in seconds ie:
* 12s -> 12 seconds
*
* 12m -> 720 seconds
*
* 12h -> 43,200 seconds
*
* 12d -> 1,036,800 seconds
*
* @param str input time
* @return time in seconds
*/
static int
string2time(char *str)
{
char *d;
int i;
double time;
/* find the length of the string */
for (i = 0; str[i] != '\0'; i++)
;
/* if there is a trailing character make sure to remove it */
if (str[i] < '0' || str[i] > '9') {
i--;
}
/* allocate space for numerical string */
d = (char *)calloc(i, sizeof(char));
strncpy(d, str, i);
/* atoi the numerical string into an int */
time = atof(d);
free(d);
/* modify the time using the last trailing character */
switch (str[i]) {
case 's': break;
case 'm': time *= 60; break;
case 'h': time *= 60 * 60; break;
case 'd': time *= 60 * 60 * 24; break;
default: return -1; break;
}
return time;
}
void
timer_subcmd(void *, int argc, char *argv[])
{
int i, l, sum;
char *s;
for (i = 1, sum = 0, l = 0; i < argc; i++) {
sum += string2time(argv[i]);
l += strlen(argv[i]);
}
/* allocate and populate a string with the length of the timer */
s = (char *)calloc(l + i - 1, sizeof(char));
for (i = 1; i < argc; i++) {
/* TODO: reorder based on magnitude */
s = strcat(s, argv[i]);
if (i != argc - 1) {
s = strcat(s, " ");
}
}
if (sum <= 0) {
exit(1);
}
/* print out the number of time */
printf("Waiting for %s...\n", s);
/* TODO: instead of sleeping implement polling that way if need be we can have
* a countdown */
sleep(sum);
puts("returned");
return;
}
static void
stopwatch_subcmd(void *, int argc, char *argv[])
{
}

105
src/subcmds/dev.c Normal file
View File

@@ -0,0 +1,105 @@
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <unistd.h>
#include "conf.h"
#include "subcmds.h"
static void
add(char *path)
{
char *wpath, *l;
FILE *f;
wpath = calloc(strlen(conf_state_path()) + 4, sizeof(char));
strcpy(wpath, conf_state_path());
strcat(wpath, "dev");
/* ensure that the file exists */
if (access(wpath, F_OK) != 0) {
fclose(fopen(wpath, "w"));
}
/* check if it already exists */
f = fopen(wpath, "r");
l = calloc(PATH_MAX, sizeof(char));
while (fgets(l, PATH_MAX, f)) {
l[strlen(l) - 1] = '\0';
if (strcmp(l, path) == 0) {
printf("'%s' is already in the list of projects\n", path);
free(l);
free(wpath);
fclose(f);
return;
}
bzero(l, PATH_MAX);
}
fclose(f);
/* add the project to the list */
f = fopen(wpath, "a");
if (fprintf(f, "%s\n", path) < 0) {
printf("failed to add new project to dev list\n");
exit(1);
}
fclose(f);
free(wpath);
return;
}
void
subcmds_dev(void *, int argc, char *argv[])
{
char cwd[PATH_MAX];
char *wpath, *editor;
if (argc > 1) {
if (strcmp(argv[1], "add") == 0) {
if (argc > 2) {
/* if the directory doesn't exist, then make it */
if (!opendir(conf_state_path())) {
if (errno == ENOENT) {
if (mkdir(conf_state_path(), 0755) < 0) {
printf("Failed to create state path: %s\n", conf_state_path());
exit(1);
}
}
}
/* add the directory to the path */
add(argv[2]);
} else {
if (getcwd(cwd, PATH_MAX) == NULL) {
exit(1);
}
/* add the directory to the path */
add(cwd);
}
} else if (strcmp(argv[1], "edit") == 0) {
wpath = calloc(strlen(conf_state_path()) + 4, sizeof(char));
strcpy(wpath, conf_state_path());
strcat(wpath, "dev");
editor = getenv("EDITOR");
if (editor) {
execvp(editor, (char *[]){ editor, wpath, NULL });
goto free_stuff;
}
printf("Failed to open editor\n");
free_stuff:
free(editor);
free(wpath);
} else if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "h") == 0) {
printf("Help text\n");
} else {
}
}
}

112
src/subcmds/motd.c Normal file
View File

@@ -0,0 +1,112 @@
#include <dirent.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include "subcmds.h"
static const char *months[12] = {
"January", "Febuary", "March", "April", "May", "June", "July", "August",
"September", "October", "November", "December"
};
static int
num_mail(char *path)
{
long newmail = 0;
int len;
DIR *d, *sd, *ssd;
struct dirent *dir;
char *spath, *sspath;
d = opendir(path);
if (d == NULL)
return -1;
while ((dir = readdir(d)) != NULL) {
if ((dir->d_type != DT_DIR && dir->d_type != DT_LNK) || dir->d_name[0] == '.')
continue;
/* allocate space for path and populate it */
len = strlen(path) + strlen(dir->d_name) + 3;
spath = (char *)malloc(sizeof(char) * len);
if (snprintf(spath, len, "%s/%s", path, dir->d_name) < 0)
return -2;
sd = opendir(spath);
if (sd == NULL)
continue;
/* find all inboxes, and new mail within */
while ((dir = readdir(sd)) != NULL) {
if (((dir->d_type != DT_DIR && dir->d_type != DT_LNK) || dir->d_name[0] == '.')
|| !strcasestr(dir->d_name, "inbox"))
continue;
len = strlen(spath) + strlen(dir->d_name) + 6;
sspath = (char *)malloc(sizeof(char) * len);
if (snprintf(sspath, len, "%s/%s/new", spath, dir->d_name) < 0)
return -3;
ssd = opendir(sspath);
free(sspath);
if (ssd == NULL)
continue;
/* count all the new mail */
while ((dir = readdir(ssd)) != NULL)
if (dir->d_name[0] != '.')
newmail++;
closedir(ssd);
}
closedir(sd);
}
free(spath);
free(dir);
closedir(d);
return newmail;
}
void
motd_subcmd(void *, int argc, char *argv[])
{
time_t t = time(NULL);
struct tm tm = *localtime(&t);
char *daysuffix;
int newmail;
/* allocate space for day string */
daysuffix = (char *)malloc(sizeof(char) * 3);
/* day of month */
if (tm.tm_mday > 3 && tm.tm_mday < 21)
snprintf(daysuffix, 3, "th");
else if ((tm.tm_mday / 1) % 10 == 1)
snprintf(daysuffix, 3, "st");
else if ((tm.tm_mday / 1) % 10 == 2)
snprintf(daysuffix, 3, "nd");
else if ((tm.tm_mday / 1) % 10 == 3)
snprintf(daysuffix, 3, "rd");
else
snprintf(daysuffix, 3, "th");
/* format and return */
printf("Today is \033[0;35m%s\033[m \033[0;36m%d%s\033[m, \033[0;33m%d\033[0m.\n",
months[tm.tm_mon], tm.tm_mday, daysuffix, tm.tm_year + 1900);
if ((newmail = num_mail(strcat(getenv("XDG_DATA_HOME"), "/mail"))) > 0) {
printf("You've got \033[0;36m%d\033[m unread \033[0;35memail%s\033[0m!\n",
newmail, newmail > 1 ? "s" : "");
}
free(daysuffix);
return;
}

9
src/subcmds/project.c Normal file
View File

@@ -0,0 +1,9 @@
#include "subcmds.h"
/* TODO: figure out how to properly store all the licenses in a nice and
* parseable way */
static void
license_subcmd(void *, int argc, char *argv[])
{
}

16
src/subcmds/subcmds.c Normal file
View File

@@ -0,0 +1,16 @@
#include <stdio.h>
#include "api.h"
#include "subcmds.h"
void
subcmds_subcmd(void *, int argc, char *argv[])
{
char **p;
int i;
p = list_subcmds();
for (i = 0; p[i]; i++) {
puts(p[i]);
}
}

9
test.lua Normal file
View File

@@ -0,0 +1,9 @@
wom.register_subcmd("helloworld", function()
print("hello world")
for i in wom.fs.dir(".") do
if wom.fs.type(i) == "directory" then
print(i)
end
end
end)