tim

Unnamed repository; edit this file 'description' to name the repository.
git clone git://edryd.org/tim
Log | Files | Refs | LICENSE

commit 3c61e28f4d58a072685d590566a48116a068e919
Author: Ed van Bruggen <edvb54@gmail.com>
Date:   Sat, 15 Apr 2017 21:21:26 -0700

Initial commit

Diffstat:
.gitignore | 22++++++++++++++++++++++
LICENSE | 18++++++++++++++++++
Makefile | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
README.md | 34++++++++++++++++++++++++++++++++++
arg.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 7+++++++
config.mk | 22++++++++++++++++++++++
data/actions.json | 4++++
data/cognition.json | 7+++++++
data/memory.json | 3+++
plugins/ddg.lua | 47+++++++++++++++++++++++++++++++++++++++++++++++
plugins/json-get.lua | 15+++++++++++++++
plugins/json-set.lua | 21+++++++++++++++++++++
plugins/rand.lua | 37+++++++++++++++++++++++++++++++++++++
plugins/weather.lua | 44++++++++++++++++++++++++++++++++++++++++++++
str.c | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
str.h | 11+++++++++++
tim-lua.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tim-lua.h | 3+++
tim.1 | 30++++++++++++++++++++++++++++++
tim.c | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tim.h | 3+++
util.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
util.h | 16++++++++++++++++
24 files changed, 1016 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,22 @@ +# swap files +*~ +*.swn +*.swo +*.swp +\#*\# + +# compiled files +tim +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.o +*.obj +*.so +*.so.* + +# misc +tags +config.h diff --git a/LICENSE b/LICENSE @@ -0,0 +1,18 @@ +zlib License + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/Makefile b/Makefile @@ -0,0 +1,79 @@ +# See LICENSE file for copyright and license details. + +include config.mk + +ifeq ($(DEBUG), 1) +CFLAGS += -O0 -g -DDEBUG +LDFLAGS += -g +else +CFLAGS += -O3 +endif + +EXE = tim +SRC = $(wildcard *.c) +OBJ = $(SRC:.c=.o) + +all: options $(EXE) + +options: + @echo $(EXE) build options: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + +.o: + @echo $(LD) $@ + @$(LD) -o $@ $< $(LDFLAGS) + +.c.o: + @echo $(CC) $< + @$(CC) -c -o $@ $< $(CFLAGS) + +${OBJ}: config.h config.mk + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +$(EXE): $(OBJ) + @echo $(CC) -o $@ + @$(CC) -o $@ $(OBJ) $(LDFLAGS) + +clean: + @echo -n cleaning ... + @rm -f $(OBJ) $(EXE) + @echo \ done + +install: all + @echo -n installing executable file to $(DESTDIR)$(PREFIX)/bin ... + @mkdir -p $(DESTDIR)$(PREFIX)/bin + @cp -f $(EXE) $(DESTDIR)$(PREFIX)/bin + @chmod 755 $(DESTDIR)$(PREFIX)/bin/$(EXE) + @echo \ done + @echo -n installing support files to $(DESTDIR)$(SHAREPREFIX)/$(EXE) ... + @mkdir -p $(DESTDIR)$(SHAREPREFIX)/$(EXE) + @cp -rf data plugins $(DESTDIR)$(SHAREPREFIX)/$(EXE) + @echo \ done + @echo -n installing manual page to $(DESTDIR)$(MANPREFIX)/man1 ... + @mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + @sed "s/VERSION/$(VERSION)/g" < $(EXE).1 > $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1 + @chmod 644 $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1 + @echo \ done + +uninstall: + @echo -n removing executable file from $(DESTDIR)$(PREFIX)/bin ... + @rm -f $(DESTDIR)$(PREFIX)/bin/$(EXE) + @echo \ done + @echo -n removing support files from $(DESTDIR)$(SHAREPREFIX)/$(EXE) ... + @rm -rf $(DESTDIR)$(SHAREPREFIX)/$(EXE) + @echo \ done + @echo -n removing manual page from $(DESTDIR)$(MANPREFIX)/man1 ... + @rm -f $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1 + @echo \ done + +update-man: + @echo -n updating man page $(EXE).1 ... + @cat README.md | sed "s/# $(EXE)/# $(EXE) 1\n\n##NAME\n\n$(EXE) /" | \ + md2man-roff | sed "s/\\[la\]/\</" | sed "s/\\[ra\]/\>/" > $(EXE).1 + @echo \ done + +.PHONY: all options clean install uninstall update-man diff --git a/README.md b/README.md @@ -0,0 +1,34 @@ +# tim - small extendable personal assistant + +## SYNOPSIS + +`tim` [**-iv**] [**CMD**] + +## DESCRIPTION + +Tim is a light weight personal assistant that is extendable with lua and JSON. + +## OPTIONS + +**-i** [*CMD*] + Enable interactive mode, more commands can be given though stdin after + last one was read and answer outputted + +**-v** + Print version info and exit + +## COMMANDS + + + +## AUTHOR + +Ed van Bruggen <edvb54@gmail.com> + +## SEE ALSO + +View source code at: <https://gitlab.com/edvb/tim> + +## LICENSE + +zlib License diff --git a/arg.h b/arg.h @@ -0,0 +1,63 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF() (brk_ = 1, atoi(argv[0])) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.def.h b/config.def.h @@ -0,0 +1,7 @@ +char *cogfile = "data/cognition.json"; +char *memfile = "data/memory.json"; +char *actfile = "data/actions.json"; +char *speechfile = "data/speech.json"; +char *vocabfile = "data/vocab.json"; + +int interactive = 0; diff --git a/config.mk b/config.mk @@ -0,0 +1,22 @@ +DEBUG=0 + +# tim version number +VERSION = 0.0.0 + +# paths +PREFIX = /usr/local +SHAREPREFIX = ${PREFIX}/share +MANPREFIX = ${PREFIX}/share/man + +# includes and libraries +INCS = -Iinclude +LIBS = -llua5.3 + +# flags +CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=600 +CFLAGS = -std=c99 -pedantic -Wall ${INCS} ${CPPFLAGS} +LDFLAGS = ${LIBS} + +# compiler and linker +CC = gcc + diff --git a/data/actions.json b/data/actions.json @@ -0,0 +1,4 @@ +{ + "hey": "Hello, sir", + "what is your name": "My name is Tim" +} diff --git a/data/cognition.json b/data/cognition.json @@ -0,0 +1,7 @@ +{ + "name": "Tim", + "humor": "10", + "energy": "0", + "anger": "0", + "formality": "100" +} diff --git a/data/memory.json b/data/memory.json @@ -0,0 +1,3 @@ +{ + "units": "imperial" +} diff --git a/plugins/ddg.lua b/plugins/ddg.lua @@ -0,0 +1,47 @@ +local http = require "socket.http" +local json = require "json" + +function urlencode(str) + if (str) then + str = string.gsub(str, "\n", "\r\n") + str = string.gsub(str, "([^%w ])", function (c) return string.format ("%%%02X", string.byte(c)) end) + str = string.gsub(str, " ", "+") + end + return str +end + +local urlq = urlencode(args) +local url = "http://api.duckduckgo.com/?q=" .. urlq .. "&format=json" + +local js = http.request(url) + +local t = json.decode(js) + +tlist = { + "Answer", + "Definition", + "AbstractText" +} + +local ret = "" +for i,v in ipairs(tlist) do + if (ret == "") then + ret = t[v] + end +end + +if (ret == "") then + local rett = {} + for i,reltopic in ipairs(t["RelatedTopics"]) do + if (reltopic["Topics"]) then + for j,topic in ipairs(reltopic["Topics"]) do + rett[#rett+1] = tostring(topic["Text"]) + end + else + rett[#rett+1] = tostring(reltopic["Text"]) + end + end + ret = table.concat(rett,"\n") +end + +return ret diff --git a/plugins/json-get.lua b/plugins/json-get.lua @@ -0,0 +1,15 @@ +local json = require "json" + +local myarg = { } +for s in string.gmatch(args, "%S+") do + myarg[#myarg+1] = s +end +local file = myarg[1] +local var = myarg[2] + +local f = io.open(file, "rb") +local js = f:read("*all") +f:close() +local t = json.decode(js) + +return t[var] diff --git a/plugins/json-set.lua b/plugins/json-set.lua @@ -0,0 +1,21 @@ +local json = require "json" + +local myarg = { } +for s in string.gmatch(args, "%S+") do + myarg[#myarg+1] = s +end +local file = myarg[1] +local var = myarg[2] +local val = myarg[3] + +local f = io.open(file, "rb") +local js = f:read("*all") +f:close() +local t = json.decode(js) +t[var] = val + +f = io.open(file, "w") +f:write(json.encode(t)) +f:close() + +return "Setting " .. var .. " to " .. val diff --git a/plugins/rand.lua b/plugins/rand.lua @@ -0,0 +1,37 @@ +math.randomseed(os.time()) +math.random(); math.random(); math.random() + +local m8ball = { + "It is certain", + "It is decidedly so", + "Without a doubt", + "Yes, definitely", + "You may rely on it", + "As I see it, yes", + "Most likely", + "Outlook good", + "Yes", + "Signs point to yes", + "Reply hazy try again", + "Ask again later", + "Better not tell you now", + "Cannot predict now", + "Concentrate and ask again", + "Don't count on it", + "My reply is no", + "My sources say no", + "Outlook not so good", + "Very doubtful" +} + +if args:find("coin") then + return math.random(0,1) == 1 and "heads" or "tails" +elseif args:find("from %d to %d") then + num1, num2 = args:match("number from (.-) to (.-)$") + return math.random(num1, num2) +elseif args:find("between %d and %d") then + num1, num2 = args:match("between (.-) and (.-)$") + return math.random(num1, num2) +elseif args:find("should") then + return m8ball[math.random(1,#m8ball)] +end diff --git a/plugins/weather.lua b/plugins/weather.lua @@ -0,0 +1,44 @@ +local http = require "socket.http" +local json = require "json" + +if not args:find("weather") then + return nil +end + +function urlencode(str) + if (str) then + str = string.gsub(str, "\n", "\r\n") + str = string.gsub(str, "([^%w ])", function (c) return string.format ("%%%02X", string.byte(c)) end) + str = string.gsub(str, " ", "+") + end + return str +end + +local f = io.open(get_path("data/memory.json"), "rb") +local js = f:read("*all") +f:close() +local t = json.decode(js) + +units = t["units"] +location = t["location"] + +local url = "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22" .. urlencode(location) .. "%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys" + +local js = http.request(url) +local t = json.decode(js) +local w = t["query"]["results"]["channel"] + +local temp = w["item"]["condition"]["temp"] +local tempunits = w["units"]["temperature"] +local city = w["location"]["city"] +local region = w["location"]["region"] +local country = w["location"]["country"] + +local place +if country == "United States" or country == "United Kingdom" then + place = city .. region +else + place = city .. ", " .. country +end + +return "It is " .. temp .. " " .. tempunits .. " out today in " .. place diff --git a/str.c b/str.c @@ -0,0 +1,189 @@ +/* See LICENSE file for copyright and license details. */ +#include <assert.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +/* compress array of strings to single string */ +char * +str_concat(char **s, int c) +{ + if (!s) die("strconcat: given null pointer"); + + int len = 0, i; + char *ret; + + for (i = 0; i < c; i++) + len += strlen(s[i]) + 1; + ret = ecalloc(len, sizeof(char)); + + strcpy(ret, s[0]); + strcat(ret, " "); + for (i = 1; i < c; i++) { + strcat(ret, s[i]); + strcat(ret, " "); + } + + return ret; +} + +/* split string s into muiltple strings by a_delim */ +char ** +str_split(const char *s, const char a_delim, int *c) +{ + if (!s) die("strsplit: given null pointer"); + + char **ret = 0; + size_t count = 0; + char *last_delim = 0; + char delim[2] = { a_delim, 0 }; /* converet a_delim into string for strtok */ + char *a_str = ecalloc(strlen(s)+1, sizeof(char)); + strcpy(a_str, s); + + /* count number of elements that will be extracted. */ + for (char *tmp = a_str; *tmp; tmp++) + if (a_delim == *tmp) { + count++; + last_delim = tmp; + } + + /* add space for trailing token. */ + count += last_delim < (a_str + strlen(a_str) - 1); + + /* add space for terminating null string so caller + * knows where the list of returned strings ends. */ + count++; + + ret = ecalloc(count, sizeof(char*)); + + if (ret) { + size_t idx = 0; + char *token = strtok(a_str, delim); + + while (token) { + assert(idx < count); + *(ret + idx++) = estrdup(token); + token = strtok(0, delim); + } + assert(idx == count - 1); + *(ret + idx) = 0; + } + + *c = count - 1; + free(a_str); + return ret; +} + +/* replace rep in orig with with */ +char * +str_replace(char *orig, char *rep, char *with) +{ + char *ret, *tmp, *ins; /* ins: the next insert point */ + int len_rep, len_with, len_front, count; /* len_front: distance between rep and end of last rep */ + + /* checks, set lens, and init */ + if (!orig || !rep) + return NULL; + len_rep = strlen(rep); + if (len_rep == 0) + return NULL; /* empty rep causes infinite loop during count */ + if (!with) + with = ""; + len_with = strlen(with); + + /* count the number of replacements needed */ + ins = orig; + for (count = 0; (tmp = strstr(ins, rep)); ++count) + ins = tmp + len_rep; + + tmp = ret = malloc(strlen(orig) + (len_with - len_rep) * count + 1); + + if (!ret) + return NULL; + + /* tmp: end of the ret string + * ins: next occurrence of rep in orig + * orig: remainder of orig after "end of rep" */ + while (count--) { + ins = strstr(orig, rep); + len_front = ins - orig; + tmp = strncpy(tmp, orig, len_front) + len_front; + tmp = strcpy(tmp, with) + len_with; + orig += len_front + len_rep; /* move to next "end of rep" */ + } + strcpy(tmp, orig); + + return ret; +} + +/* remove tailing or leading white space from s */ +char * +str_trim(char *s) +{ + char *end; + + /* trim leading space */ + while (isspace(*s)) s++; + + if (*s == 0) /* all spaces? */ + return s; + + /* trim trailing space */ + end = s + strlen(s) - 1; + while (end > s && isspace(*end)) end--; + + /* write new null terminator */ + *(end+1) = 0; + + return s; +} + +char * +str_rmspaces(char *s) +{ + int c = 0, d = 0; + char *start = emalloc(strlen(s)+1); + + while (*(s+c) != '\0') { + if (*(s+c) == ' ') { + int temp = c + 1; + if (*(s+temp) != '\0') { + while (*(s+temp) == ' ' && *(s+temp) != '\0') { + if (*(s+temp) == ' ') c++; + temp++; + } + } + } + *(start+d) = *(s+c); + c++; + d++; + } + *(start+d)= '\0'; + + return start; +} + +/* return if str is in the list */ +int +strinlist(char *str, char **list, int listc) +{ + if (!str || !list) return 0; + int i; + for (i = 0; i < listc; i++) + if (list[i] && strcmp(str, list[i]) == 0) + return 1; + return 0; +} + +int +charinstr(char c, char *str) +{ + if (!str) return 0; + for (; *str; str++) + if (*str == c) + return 1; + return 0; +} diff --git a/str.h b/str.h @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ + +#define STREMPTY(s) !s || strcmp(s, "") == 0 + +char *str_concat(char **s, int c); +char **str_split(const char *s, const char a_delim, int *c); +char *str_replace(char *orig, char *rep, char *with); +char *str_trim(char *s); +char *str_rmspaces(char *s); +int strinlist(char *str, char **list, int listc); +int charinstr(char c, char *str); diff --git a/tim-lua.c b/tim-lua.c @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "lua5.3/lua.h" +#include "lua5.3/lualib.h" +#include "lua5.3/lauxlib.h" + +#include "util.h" +#include "tim.h" +#include "tim-lua.h" + +static int +tim_lua_get_path(lua_State *L) +{ + const char *fname = luaL_checkstring(L, 1); + + lua_pushstring(L, get_path(fname)); + + return 1; +} + +char * +tim_lua_run(char *script, char *args) +{ + char *ret = NULL; + lua_State *L; + + /* init script */ + L = luaL_newstate(); + luaL_openlibs(L); + if (luaL_loadfile(L, script)) + die("tim_lua: couldn't load file: %s\n", lua_tostring(L, -1)); + + /* add variables */ + lua_pushstring(L, args); + lua_setglobal(L, "args"); + + /* add functions */ + lua_pushcfunction(L, tim_lua_get_path); + lua_setglobal(L, "get_path"); + + /* run script */ + if (lua_pcall(L, 0, LUA_MULTRET, 0)) + die("tim_lua: failed to run script: %s", lua_tostring(L, -1)); + + /* get the returned value */ + const char *tmp = lua_tostring(L, -1); + if (tmp) + ret = strdup(tmp); + lua_pop(L, 1); /* take the returned value out of the stack */ + + /* cleanup */ + lua_close(L); + + return ret; +} diff --git a/tim-lua.h b/tim-lua.h @@ -0,0 +1,3 @@ +/* See LICENSE file for copyright and license details. */ + +char *tim_lua_run(char *script, char *args); diff --git a/tim.1 b/tim.1 @@ -0,0 +1,30 @@ +.TH tim 1 +.SH NAME +.PP +tim \- small extendable personal assistant +.SH SYNOPSIS +.PP +\fB\fCtim\fR [\fB\-iv\fP] [\fBCMD\fP] +.SH DESCRIPTION +.PP +Tim is a light weight personal assistant that is extendable with lua and JSON. +.SH OPTIONS +.PP +\fB\-i\fP [\fICMD\fP] + Enable interactive mode, more commands can be given though stdin after + last one was read and answer outputted +.PP +\fB\-v\fP + Print version info and exit +.SH COMMANDS +.SH AUTHOR +.PP +Ed van Bruggen +\<edvb54@gmail.com\> +.SH SEE ALSO +.PP +View source code at: +\<https://gitlab.com/edvb/tim\> +.SH LICENSE +.PP +zlib License diff --git a/tim.c b/tim.c @@ -0,0 +1,217 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <limits.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "arg.h" +#include "str.h" +#include "tim-lua.h" +#include "util.h" + +/* macros */ +#define MAX_CMD 2048 + +/* typedefs */ + +/* functions */ +char *get_tim_ans(char *s); + +char *fmt_cmd(char *s); +void run_cmd(void); +void print_out(void); + +void setup(void); +void cleanup(void); +void usage(void); + +/* variables */ +char *argv0; + +char *in; +char *cmd; +char *out; +int fmtout = 1; + +#include "config.h" + +/* returns the path to fname based on: + * 1) $XDG_CONFIG_HOME/tim/ (~/.config/tim/) + * 3) /etc/tim/ (system wide config) + * 2) /usr/local/share/tim/ (default when installed) + * 4) ./ (current directory) + */ +char * +get_path(const char *fname) +{ + if (!fname) return NULL; + int i; + static char ret[PATH_MAX]; + const char *paths[] = { "/etc/tim/", "/usr/local/share/tim/", "./" }; + const char *home = getenv("HOME"); + const char *xdg_config = getenv("XDG_CONFIG_HOME"); + struct passwd *pw; + +#ifdef DEBUG + /* if DEBUG is set check current directory first */ + strcpy(ret, "./"); + strcat(ret, fname); + if (access(ret, F_OK) != -1) + return ret; +#endif + + /* try to get users home directory */ + if (!home || !*home) + if ((pw = getpwuid(getuid()))) + home = pw->pw_dir; + + /* get XDG_CONFIG_HOME path */ + if (xdg_config) + snprintf(ret, sizeof(ret), "%s/tim/%s", xdg_config, fname); + else if (home && *home) + snprintf(ret, sizeof(ret), "%s/.config/tim/%s", home, fname); + + /* try rest of paths in paths[] */ + for (i = 0; access(ret, F_OK) == -1; i++) { + if (i >= LEN(paths)) die("%s: file '%s' not found", argv0, fname); + strcpy(ret, paths[i]); + strcat(ret, fname); + } + + return ret; +} + +char * +get_tim_ans(char *s) +{ + if (!s) return NULL; + char *ans = NULL; + int c, i = 2; + char **cmds; + char *fname = NULL; + char args[MAX_CMD]; + + s = str_replace(s, "to", ""); + s = str_rmspaces(s); + cmds = str_split(s, ' ', &c); + free(s); + + if (c > 2) { + if (strcmp(cmds[1], "my") == 0) + fname = get_path(memfile); + else if (strcmp(cmds[1], "your") == 0) + fname = get_path(cogfile); + else { + fname = get_path(cogfile); + i--; + } + } + + if (strcmp(cmds[0], "get") == 0 && c > 2) { + snprintf(args, sizeof(args), "%s %s", fname, cmds[i]); + ans = tim_lua_run(get_path("plugins/json-get.lua"), args); + } else if (strcmp(cmds[0], "set") == 0 && c > 3) { + snprintf(args, sizeof(args), "%s %s %s", fname, cmds[i], cmds[i+1]); + ans = tim_lua_run(get_path("plugins/json-set.lua"), args); + } + + return ans; +} + +char * +fmt_cmd(char *s) +{ + int i; + for (i = 0; s[i]; i++) + s[i] = tolower(s[i]); /* TODO not for proper nouns */ + return s; +} + +/* generate tim's response */ +void +run_cmd(void) +{ + char args[MAX_CMD]; + cmd = fmt_cmd(in); + snprintf(args, sizeof(args), "%s %s", get_path(actfile), cmd); + out = tim_lua_run(get_path("plugins/json-get.lua"), args); + if (STREMPTY(out)) + out = get_tim_ans(cmd); + if (STREMPTY(out)) + out = tim_lua_run(get_path("plugins/weather.lua"), cmd); + if (STREMPTY(out)) + out = tim_lua_run(get_path("plugins/rand.lua"), cmd); + if (STREMPTY(out)) { + out = tim_lua_run(get_path("plugins/ddg.lua"), in); + fmtout = 0; + } + if (STREMPTY(out)) + out = estrdup("Sorry I didn't get that"); +} + +/* print tim's respond */ +void +print_out(void) +{ + if (!out) return; + printf("%s\n", out); + out = NULL; +} + +/* allocate strs */ +void +setup(void) +{ + in = ecalloc(MAX_CMD, sizeof(char)); + out = ecalloc(MAX_CMD, sizeof(char)); +} + +/* free memory */ +void +cleanup(void) +{ + free(in); + free(out); +} + +void +usage(void) +{ + die("usage: %s [-iv] [CMD]", argv0); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case 'i': + interactive = 1; + break; + case 'v': + printf("%s v%s\n", argv0, VERSION); + return 0; + default: + usage(); + } ARGEND; + + setup(); + + do { + if (argc > 0) { + in = str_concat(argv, argc); + argc = 0; + } else + fgets(in, MAX_CMD, stdin); + str_trim(in); + run_cmd(); + print_out(); + } while (interactive && !feof(stdin)); + + cleanup(); + + return 0; +} diff --git a/tim.h b/tim.h @@ -0,0 +1,3 @@ +/* See LICENSE file for copyright and license details. */ + +char *get_path(const char *fname); diff --git a/util.c b/util.c @@ -0,0 +1,67 @@ +/* See LICENSE file for copyright and license details. */ +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc: out of memory"); + + return p; +} + +void * +emalloc(size_t size) +{ + void *p; + + if (!(p = malloc(size))) + die("malloc: out of memory"); + + return p; +} + +void * +erealloc(void *p, size_t size) +{ + if (!(p = realloc(p, size))) + die("realloc: out of memory"); + + return p; +} + +char * +estrdup(char *s) +{ + if (!(s = strdup(s))) + die("strdup: out of memory"); + + return s; +} + +void +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/util.h b/util.h @@ -0,0 +1,16 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) +#define LEN(x) ((int)(sizeof (x) / sizeof *(x))) + +#define DUMPD(x) printf("%s: %d\n", #x, x) +#define DUMPS(x) printf("%s: %s\n", #x, x) + +void *ecalloc(size_t nmemb, size_t size); +void *emalloc(size_t size); +void *erealloc(void *p, size_t size); +char *estrdup(char *s); + +void die(const char *errstr, ...);