commit 3c61e28f4d58a072685d590566a48116a068e919
Author: Ed van Bruggen <edvb54@gmail.com>
Date: Sat, 15 Apr 2017 21:21:26 -0700
Initial commit
Diffstat:
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, ...);