quakeforge/libs/gib/gib_builtin.c
Brian Koropoff 0e0d8bd542 Added support for enclosing embedded commands in $() and cleaned up the
parser a bit to accomodate this.  Backslashes in double quotes are now only
removed if they escape a character that can't be written normally, or
another backslash.  Removed start position support from string::findsub
since variable slices can be used instead.  Added support for regular
expressions in the form of regex::match, regex::replace, and
regex::extract.  Checked in regex.c from GNU regex 0.12 for platforms that
do not have regex functions in their standard library. Two minor changes
were made to this file to fix gcc warnings.  Prepared the path transform
function for a change to a filesystem rooted at fs_userpath instead of the
current gamedir, but these changes are commented out pending security
considerations.
2002-11-19 04:15:36 +00:00

970 lines
24 KiB
C

/*
#FILENAME#
#DESCRIPTION#
Copyright (C) 2002 #AUTHOR#
Author: #AUTHOR#
Date: #DATE#
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
static const char rcsid[] =
"$Id$";
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <fnmatch.h>
#include <errno.h>
#include "QF/cvar.h"
#include "QF/quakeio.h"
#include "QF/quakefs.h"
#include "QF/zone.h"
#include "QF/va.h"
#include "QF/sys.h"
#include "QF/cmd.h"
#include "QF/cbuf.h"
#include "QF/hash.h"
#include "QF/dstring.h"
#include "QF/gib_parse.h"
#include "QF/gib_builtin.h"
#include "QF/gib_buffer.h"
#include "QF/gib_function.h"
#include "QF/gib_vars.h"
#include "QF/gib_thread.h"
#include "regex.h"
hashtab_t *gib_builtins;
/*
Hashtable callbacks
*/
const char *
GIB_Builtin_Get_Key (void *ele, void *ptr)
{
return ((gib_builtin_t *)ele)->name->str;
}
void
GIB_Builtin_Free (void *ele, void *ptr)
{
gib_builtin_t *b;
b = (gib_builtin_t *) ele;
dstring_delete (b->name);
free (b);
}
/*
GIB_Builtin_Add
Registers a new builtin GIB command.
*/
void
GIB_Builtin_Add (const char *name, void (*func) (void), enum gib_builtin_type_e type)
{
gib_builtin_t *new;
if (!gib_builtins)
gib_builtins = Hash_NewTable (1024, GIB_Builtin_Get_Key, GIB_Builtin_Free, 0);
new = calloc (1, sizeof (gib_builtin_t));
new->func = func;
new->name = dstring_newstr();
new->type = type;
dstring_appendstr (new->name, name);
Hash_Add (gib_builtins, new);
}
/*
GIB_Builtin_Find
Looks up the builtin name in the builtin hash,
returning a pointer to the struct on success,
zero otherwise.
*/
gib_builtin_t *
GIB_Builtin_Find (const char *name)
{
if (!gib_builtins)
return 0;
return (gib_builtin_t *) Hash_Find (gib_builtins, name);
}
/*
GIB_Arg_Strip_Delim
Strips any wrapping characters off of the
specified argument. Useful for GIB_BUILTIN_NOPROCESS
or GIB_BUILTIN_FIRSTONLY builtins.
*/
void
GIB_Arg_Strip_Delim (unsigned int arg)
{
char *p = cbuf_active->args->argv[arg]->str;
if (*p == '{' || *p == '\"') {
dstring_snip (cbuf_active->args->argv[arg], 0, 1);
p[strlen(p)-1] = 0;
}
}
void
GIB_Return (const char *str)
{
if (GIB_DATA(cbuf_active)->type != GIB_BUFFER_PROXY)
return;
dstring_clearstr (GIB_DATA(cbuf_active->up)->ret.retval);
dstring_appendstr (GIB_DATA(cbuf_active->up)->ret.retval, str);
GIB_DATA(cbuf_active->up)->ret.available = true;
}
/*
GIB Builtin functions
See GIB docs for information.
*/
void
GIB_Function_f (void)
{
if (GIB_Argc () != 3)
Cbuf_Error ("syntax",
"function: invalid syntax\n"
"usage: function function_name {program}");
else
GIB_Function_Define (GIB_Argv(1), GIB_Argv(2));
}
void
GIB_Function_Get_f (void)
{
if (GIB_Argc () != 2)
Cbuf_Error ("syntax",
"function.get: invalid syntax\n"
"usage: function::get function_name");
else {
gib_function_t *f;
if ((f = GIB_Function_Find (GIB_Argv (1))))
GIB_Return (f->program->str);
else
GIB_Return ("");
}
}
void
GIB_Local_f (void)
{
int i;
if (GIB_Argc () < 2)
Cbuf_Error ("syntax",
"local: invalid syntax\n"
"usage: local varname1 varname2 varname3 [...]");
else
for (i = 1; i < GIB_Argc(); i++)
GIB_Var_Set_Local (cbuf_active, GIB_Argv(i), "");
}
void
GIB_Global_f (void)
{
int i;
if (GIB_Argc () < 2)
Cbuf_Error ("syntax",
"global: invalid syntax\n"
"usage: global varname1 varname2 varname3 [...]");
else
for (i = 1; i < GIB_Argc(); i++)
GIB_Var_Set_Global (GIB_Argv(i), "");
}
void
GIB_Global_Delete_f (void)
{
if (GIB_Argc () != 2)
Cbuf_Error ("syntax",
"global::delete: invalid syntax\n"
"usage: global.delete variable");
GIB_Var_Free_Global (GIB_Argv(1));
}
void
GIB_Return_f (void)
{
cbuf_t *sp;
if (GIB_Argc () > 2)
Cbuf_Error ("syntax",
"return: invalid syntax\n"
"usage: return <value>");
else {
sp = cbuf_active;
while (sp->interpreter == &gib_interp && GIB_DATA(sp)->type == GIB_BUFFER_LOOP) { // Get out of loops
GIB_DATA(sp)->type = GIB_BUFFER_PROXY;
dstring_clearstr (sp->buf);
dstring_clearstr (sp->line);
sp = sp->up;
}
dstring_clearstr (sp->buf);
dstring_clearstr (sp->line);
if (GIB_Argc () == 1)
return;
if (!sp->up || // Nothing above us on the stack
GIB_DATA(sp->up)->type != GIB_BUFFER_PROXY || // No proxy buffer created
!sp->up->up || // Nothing above proxy buffer on the stack
sp->up->up->interpreter != &gib_interp || // Not a GIB buffer
!GIB_DATA(sp->up->up)->ret.waiting) // Buffer doesn't want a return value
Sys_Printf("Warning: unwanted return value discarded.\n"); // Not a serious error
else {
dstring_clearstr (GIB_DATA(sp->up->up)->ret.retval);
dstring_appendstr (GIB_DATA(sp->up->up)->ret.retval, GIB_Argv(1));
GIB_DATA(sp->up->up)->ret.available = true;
}
}
}
void
GIB_If_f (void)
{
int condition;
if ((!strcmp (GIB_Argv (3), "else") && GIB_Argc() >= 5) || // if condition {program} else ...
(GIB_Argc() == 3)) { // if condition {program}
condition = atoi(GIB_Argv(1));
if (!strcmp (GIB_Argv(0), "ifnot"))
condition = !condition;
if (condition) {
GIB_Arg_Strip_Delim (2);
Cbuf_InsertText (cbuf_active, GIB_Argv(2));
} else if (GIB_Argc() == 5) {
GIB_Arg_Strip_Delim (4);
Cbuf_InsertText (cbuf_active, GIB_Argv(4));
} else if (GIB_Argc() > 5)
Cbuf_InsertText (cbuf_active, GIB_Args (4));
} else
Cbuf_Error ("syntax",
"if: invalid syntax\n"
"usage: if condition {program} [else ...]"
);
}
void
GIB_While_f (void)
{
if (GIB_Argc() != 3) {
Cbuf_Error ("syntax",
"while: invalid syntax\n"
"usage: while condition {program}"
);
} else {
cbuf_t *sub = Cbuf_New (&gib_interp);
GIB_DATA(sub)->type = GIB_BUFFER_LOOP;
GIB_DATA(sub)->locals = GIB_DATA(cbuf_active)->locals;
GIB_DATA(sub)->loop_program = dstring_newstr ();
if (cbuf_active->down)
Cbuf_DeleteStack (cbuf_active->down);
cbuf_active->down = sub;
sub->up = cbuf_active;
GIB_Arg_Strip_Delim (2);
dstring_appendstr (GIB_DATA(sub)->loop_program, va("ifnot %s break;%s", GIB_Argv (1), GIB_Argv (2)));
Cbuf_AddText (sub, GIB_DATA(sub)->loop_program->str);
cbuf_active->state = CBUF_STATE_STACK;
}
}
void
GIB_Field_Get_f (void)
{
unsigned int field;
char *list, *end;
const char *ifs;
if (GIB_Argc() < 3 || GIB_Argc() > 4) {
Cbuf_Error ("syntax",
"field::get: invalid syntax\n"
"usage: field::get list element [ifs]"
);
return;
}
field = atoi (GIB_Argv(2));
if (GIB_Argc() == 4)
ifs = GIB_Argv (3);
else if (!(ifs = GIB_Var_Get_Local (cbuf_active, "ifs")))
ifs = " \t\n\r";
for (list = GIB_Argv(1); *list && strchr(ifs, *list); list++);
while (field) {
while (!strchr(ifs, *list))
list++;
while (*list && strchr(ifs, *list))
list++;
if (!*list) {
GIB_Return ("");
return;
}
field--;
}
for (end = list; !strchr(ifs, *end); end++);
*end = 0;
GIB_Return (list);
}
void
GIB___For_f (void)
{
char *end = 0, old = 0;
if (!GIB_DATA(cbuf_active)->loop_list_p[0]) {
Cbuf_InsertText (cbuf_active, "break;");
return;
}
if (!GIB_DATA(cbuf_active)->loop_ifs_p[0]) {
end = GIB_DATA(cbuf_active)->loop_list_p;
old = end[1];
end[1] = 0;
GIB_Var_Set_Local (cbuf_active, GIB_DATA(cbuf_active)->loop_var_p, GIB_DATA(cbuf_active)->loop_list_p++);
end[1] = old;
return;
}
for (end = GIB_DATA(cbuf_active)->loop_list_p; !strchr(GIB_DATA(cbuf_active)->loop_ifs_p, *end); end++);
if (*end) {
old = *end;
*end = 0;
}
GIB_Var_Set_Local (cbuf_active, GIB_DATA(cbuf_active)->loop_var_p, GIB_DATA(cbuf_active)->loop_list_p);
if (old)
*end = old;
while (*end && strchr(GIB_DATA(cbuf_active)->loop_ifs_p, *end))
end++;
GIB_DATA(cbuf_active)->loop_list_p = end;
}
void
GIB_For_f (void)
{
if (strcmp ("in", GIB_Argv (2)) ||
(GIB_Argc() == 7 && strcmp ("by", GIB_Argv(4))) ||
(GIB_Argc() != 5 && GIB_Argc() != 7)) {
Cbuf_Error ("syntax",
"for: invalid syntax\n"
"usage: for variable in list {program}"
);
} else if (GIB_Argv (3)[0]) {
char *ll;
const char *ifs;
cbuf_t *sub = Cbuf_New (&gib_interp);
// Create loop buffer
GIB_DATA(sub)->type = GIB_BUFFER_LOOP;
GIB_DATA(sub)->locals = GIB_DATA(cbuf_active)->locals;
GIB_DATA(sub)->loop_program = dstring_newstr ();
GIB_DATA(sub)->loop_data = dstring_newstr ();
if (cbuf_active->down)
Cbuf_DeleteStack (cbuf_active->down);
cbuf_active->down = sub;
sub->up = cbuf_active;
// Store all for-loop data in one big buffer (easy to clean up)
dstring_appendstr (GIB_DATA(sub)->loop_data, GIB_Argv(3));
dstring_append (GIB_DATA(sub)->loop_data, GIB_Argv(1), strlen(GIB_Argv(1))+1);
if (GIB_Argc() == 7)
ifs = GIB_Argv (5);
else if (!(ifs = GIB_Var_Get_Local (cbuf_active, "ifs")))
ifs = " \n\r\t";
dstring_append (GIB_DATA(sub)->loop_data, ifs, strlen(ifs)+1);
// Store pointers to data
ll = GIB_DATA(sub)->loop_data->str;
while (isspace ((byte) *ll))
ll++;
GIB_DATA(sub)->loop_list_p = ll; // List to iterate through
GIB_DATA(sub)->loop_var_p = GIB_DATA(sub)->loop_data->str + strlen(GIB_Argv(3))+1; // Var to use
GIB_DATA(sub)->loop_ifs_p = GIB_DATA(sub)->loop_var_p + strlen(GIB_Argv(1))+1; // Internal field separator
dstring_appendstr (GIB_DATA(sub)->loop_program, "__for;");
dstring_appendstr (GIB_DATA(sub)->loop_program, GIB_Argc() == 7 ? GIB_Argv (6) : GIB_Argv(4));
Cbuf_AddText (sub, GIB_DATA(sub)->loop_program->str);
cbuf_active->state = CBUF_STATE_STACK;
}
}
void
GIB_Break_f (void)
{
if (GIB_DATA(cbuf_active)->type != GIB_BUFFER_LOOP)
Cbuf_Error ("syntax",
"break attempted outside of a loop"
);
else {
GIB_DATA(cbuf_active)->type = GIB_BUFFER_PROXY; // If we set it to normal locals will get freed
dstring_clearstr (cbuf_active->buf);
}
}
// Note: this is a standard console command, not a GIB builtin
void
GIB_Runexported_f (void)
{
gib_function_t *f;
if (!(f = GIB_Function_Find (Cmd_Argv (0))))
Sys_Printf ("Error: No function found for exported command \"%s\".\n"
"This is most likely a bug, please report it to"
"The QuakeForge developers.", Cmd_Argv(0));
else {
cbuf_t *sub = Cbuf_New (&gib_interp);
GIB_Function_Execute (sub, f, cbuf_active->args);
cbuf_active->down = sub;
sub->up = cbuf_active;
cbuf_active->state = CBUF_STATE_STACK;
}
}
void
GIB_Function_Export_f (void)
{
gib_function_t *f;
int i;
if (GIB_Argc() < 2)
Cbuf_Error ("syntax",
"function::export: invalid syntax\n"
"usage: function::export function1 function2 function3 [...]");
for (i = 1; i < GIB_Argc(); i++) {
if (!(f = GIB_Function_Find (GIB_Argv (i))))
Cbuf_Error ("function", "function::export: function '%s' not found", GIB_Argv (i));
else if (!f->exported) {
Cmd_AddCommand (f->name->str, GIB_Runexported_f, "Exported GIB function.");
f->exported = true;
}
}
}
void
GIB_String_Length_f (void)
{
if (GIB_Argc() != 2)
Cbuf_Error ("syntax",
"string::length: invalid syntax\n"
"usage: string::length string");
else
GIB_Return (va("%i", (int) strlen(GIB_Argv(1))));
}
void
GIB_String_Equal_f (void)
{
if (GIB_Argc() != 3)
Cbuf_Error ("syntax",
"string::equal: invalid syntax\n"
"usage: string::equal string1 string2");
else
GIB_Return (va("%i", !strcmp(GIB_Argv(1), GIB_Argv(2))));
}
void
GIB_String_Findsub_f (void)
{
char *haystack, *res;
if (GIB_Argc() != 3) {
GIB_USAGE ("string substr");
return;
}
haystack = GIB_Argv(1);
if ((res = strstr(haystack, GIB_Argv(2))))
GIB_Return (va("%lu", (unsigned long int)(res - haystack)));
else
GIB_Return ("-1");
}
inline unsigned int
GIB_Regex_Apply_Match (regmatch_t match[10], dstring_t *dstr, unsigned int ofs, const char *replace)
{
int i, start, len, sub, rlen = strlen(replace);
char *matched;
start = match[0].rm_so+ofs;
len = match[0].rm_eo - match[0].rm_so;
// Save matched pattern space
matched = calloc (len + 1, sizeof(char));
memcpy (matched, dstr->str+start, match[0].rm_eo - match[0].rm_so);
dstring_replace (dstr, start, len, replace, rlen);
for (i = start; i < start+rlen; i++) {
if (dstr->str[i] == '\\') {
if (dstr->str[i+1] == '&') {
dstring_snip (dstr, i, 1);
rlen--;
continue;
}
if (isdigit ((byte) dstr->str[i+1])) {
if (i && dstr->str[i-1] == '\\') { // Escaped, not a true back reference
dstring_snip (dstr, i, 1);
rlen--;
continue;
}
sub = dstr->str[i+1] - '0';
if (match[sub].rm_so != -1) {
dstring_replace (dstr, i, 2, matched+match[sub].rm_so, match[sub].rm_eo - match[sub].rm_so);
rlen += match[sub].rm_eo - match[sub].rm_so - 2;
} else {
dstring_snip (dstr, i, 2);
rlen -= 2;
}
}
} else if (dstr->str[i] == '&') {
dstring_replace (dstr, i, 1, matched, len);
rlen += strlen(matched) - 1;
}
}
free (matched);
return rlen + match[0].rm_so;
}
void
GIB_Regex_Match_f (void)
{
regex_t *reg;
int res;
char errbuf[1024];
if (GIB_Argc() != 3) {
GIB_USAGE ("string regex");
return;
}
reg = calloc (1, sizeof (regex_t));
if ((res = regcomp(reg, GIB_Argv(2), REG_NOSUB | REG_EXTENDED))) {
regerror(res, reg, errbuf, sizeof(errbuf));
Cbuf_Error ("regex", "%s: %s", GIB_Argv(0), errbuf);
} else if (regexec(reg, GIB_Argv(1), 0, 0, 0))
GIB_Return ("0");
else
GIB_Return ("1");
regfree (reg);
free (reg);
}
void
GIB_Regex_Replace_f (void)
{
regex_t *reg;
int res, ofs, len;//, e;
char errbuf[1024];
regmatch_t match[10];
if (GIB_Argc() < 4 || GIB_Argc() > 5) {
GIB_USAGE ("string regex replacement [options]");
return;
}
ofs = 0;
len = strlen (GIB_Argv(3));
reg = calloc (1, sizeof (regex_t));
if ((res = regcomp(reg, GIB_Argv(2), REG_EXTENDED))) {
regerror(res, reg, errbuf, sizeof(errbuf));
Cbuf_Error ("regex", "%s: %s", GIB_Argv(0), errbuf);
} else while (!regexec(reg, GIB_Argv(1)+ofs, 10, match, ofs > 0 ? REG_NOTBOL : 0) && match[0].rm_eo) {
ofs += GIB_Regex_Apply_Match (match, GIB_Argd(1), ofs, GIB_Argv(3));
}
regfree (reg);
free (reg);
GIB_Return (GIB_Argv(1));
}
void
GIB_Regex_Extract_f (void)
{
regex_t *reg;
char errbuf[1024];
regmatch_t *match;
int i, res;
char o;
if (GIB_Argc() < 4) {
GIB_USAGE ("string regex var1 [var2 var3 ...]");
return;
}
match = calloc (GIB_Argc() - 3, sizeof(regmatch_t));
reg = calloc (1, sizeof (regex_t));
if ((res = regcomp(reg, GIB_Argv(2), REG_EXTENDED))) {
regerror(res, reg, errbuf, sizeof(errbuf));
Cbuf_Error ("regex", "%s: %s", GIB_Argv(0), errbuf);
} else if (!regexec(reg, GIB_Argv(1), GIB_Argc() - 3, match, 0) && match[0].rm_eo) {
for (i = 0; i < GIB_Argc() - 3; i++) {
if (match[i].rm_so != -1 && *GIB_Argv(i+3)) {
o = GIB_Argv(1)[match[i].rm_eo];
GIB_Argv(1)[match[i].rm_eo] = 0;
GIB_Var_Set_Local (cbuf_active, GIB_Argv(i+3), GIB_Argv(1)+match[i].rm_so);
GIB_Argv(1)[match[i].rm_eo] = o;
}
}
GIB_Return (va("%lu", (unsigned long) match[0].rm_eo));
} else
GIB_Return ("-1");
regfree (reg);
free (reg);
free (match);
}
void
GIB_Thread_Create_f (void)
{
if (GIB_Argc() != 2)
Cbuf_Error ("syntax",
"thread::create: invalid syntax\n"
"usage: thread::create program");
else {
gib_thread_t *thread = GIB_Thread_New ();
Cbuf_AddText (thread->cbuf, GIB_Argv(1));
GIB_Thread_Add (thread);
GIB_Return (va("%lu", thread->id));
}
}
void
GIB_Thread_Kill_f (void)
{
if (GIB_Argc() != 2)
Cbuf_Error ("syntax",
"thread::kill: invalid syntax\n"
"usage: thread::kill id");
else {
gib_thread_t *thread;
cbuf_t *cur;
unsigned long int id = strtoul (GIB_Argv(1), 0, 10);
thread = GIB_Thread_Find (id);
if (!thread) {
Cbuf_Error ("thread", "thread.kill: thread %ul does not exist.", id);
return;
}
for (cur = thread->cbuf; cur; cur = cur->down) {
// Kill all loops
if (GIB_DATA(cur)->type == GIB_BUFFER_LOOP)
GIB_DATA(cur)->type = GIB_BUFFER_PROXY; // Proxy to prevent shared locals being freed
dstring_clearstr (cur->line);
dstring_clearstr (cur->buf);
}
}
}
/* File access */
int (*GIB_File_Transform_Path) (dstring_t *path) = NULL;
int
GIB_File_Transform_Path_Null (dstring_t *path)
{
char *s;
// Convert backslash to forward slash
while ((s = strchr (path->str, '\\')))
*s = '/';
return 0;
}
int
GIB_File_Transform_Path_Secure (dstring_t *path)
{
char *s /* , e_dir[MAX_OSPATH] */;
while ((s = strchr (path->str, '\\')))
*s = '/';
if (Sys_PathType (path->str) != PATHTYPE_RELATIVE_BELOW)
return -1;
/* Qexpand_squiggle (fs_userpath->string, e_dir); */
dstring_insertstr (path, 0, "/");
dstring_insertstr (path, 0, /* e_dir */ com_gamedir);
return 0;
}
void
GIB_File_Read_f (void)
{
QFile *file;
char *path, *contents = 0;
int len;
if (GIB_Argc () != 2) {
Cbuf_Error ("syntax",
"file::read: invalid syntax\n"
"usage: file::read path_and_filename");
return;
}
if (!*GIB_Argv (1)) {
Cbuf_Error ("file",
"file::read: null filename provided");
return;
}
if (GIB_File_Transform_Path (GIB_Argd(1))) {
Cbuf_Error ("access",
"file::read: access to %s denied", GIB_Argv(1));
return;
}
path = GIB_Argv (1);
file = Qopen (path, "r");
if (file) {
len = Qfilesize (file);
contents = (char *) malloc (len + 1);
SYS_CHECKMEM (contents);
contents[len] = 0;
Qread (file, contents, len);
Qclose (file);
}
if (!contents) {
Cbuf_Error ("file",
"file::read: could not open %s for reading: %s", path, strerror (errno));
return;
}
GIB_Return (contents);
free (contents);
}
void
GIB_File_Write_f (void)
{
QFile *file;
char *path;
if (GIB_Argc () != 3) {
Cbuf_Error ("syntax",
"file::write: invalid syntax\n"
"usage: file::write path_and_filename data");
return;
}
if (!*GIB_Argv(1)) {
Cbuf_Error ("file",
"file::write: null filename provided");
return;
}
if (GIB_File_Transform_Path (GIB_Argd(1))) {
Cbuf_Error ("access",
"file::write: access to %s denied", GIB_Argv(1));
return;
}
path = GIB_Argv(1);
if (!(file = Qopen (path, "w"))) {
Cbuf_Error ("file",
"file::write: could not open %s for writing: %s", path, strerror (errno));
return;
}
Qprintf (file, "%s", GIB_Argv (2));
Qclose (file);
}
void
GIB_File_Find_f (void)
{
DIR *directory;
struct dirent *entry;
char *path, *glob, *s;
const char *ifs;
dstring_t *list;
if (GIB_Argc () != 2) {
Cbuf_Error ("syntax",
"file::find: invalid syntax\n"
"usage: file::find path_and_glob");
return;
}
if (GIB_File_Transform_Path (GIB_Argd(1))) {
Cbuf_Error ("access",
"file::find: access to %s denied", GIB_Argv(1));
return;
}
path = GIB_Argv(1);
s = strrchr (path, '/');
if (!s) { // No slash in path
glob = path; // The glob is the entire argument
path = "."; // The path is the current directory
} else {
*s = 0; // Split the string at the final slash
glob = s+1;
if (!*path) // If we now have a null path...
path = "/"; // we wanted the filesystem root in unix
}
directory = opendir (path);
if (!directory) {
Cbuf_Error ("file",
"file.find: could not open directory %s: %s", path, strerror (errno));
return;
}
list = dstring_newstr ();
if (!(ifs = GIB_Var_Get_Local (cbuf_active, "ifs")))
ifs = "\n"; // Newlines don't appear in filenames and are part of the default ifs
while ((entry = readdir (directory))) {
if (strcmp (entry->d_name, ".") &&
strcmp (entry->d_name, "..") &&
!fnmatch (glob, entry->d_name, 0)) {
dstring_appendsubstr (list, ifs, 1);
dstring_appendstr (list, entry->d_name);
}
}
if (list->str[0])
GIB_Return (list->str + 1);
else
GIB_Return ("");
dstring_delete (list);
}
void
GIB_File_Move_f (void)
{
char *path1, *path2;
if (GIB_Argc () != 3) {
Cbuf_Error ("syntax",
"file::move: invalid syntax\n"
"usage: file::move from_file to_file");
return;
}
if (GIB_File_Transform_Path (GIB_Argd(1))) {
Cbuf_Error ("access",
"file::move: access to %s denied", GIB_Argv(1));
return;
}
if (GIB_File_Transform_Path (GIB_Argd(2))) {
Cbuf_Error ("access",
"file::move: access to %s denied", GIB_Argv(2));
return;
}
path1 = GIB_Argv(1);
path2 = GIB_Argv(2);
if (Qrename (path1, path2))
Cbuf_Error ("file",
"file::move: could not move %s to %s: %s",
path1, path2, strerror(errno));
}
void
GIB_File_Delete_f (void)
{
char *path;
if (GIB_Argc () != 2) {
Cbuf_Error ("syntax",
"file::delete: invalid syntax\n"
"usage: file::delete file");
return;
}
if (GIB_File_Transform_Path (GIB_Argd(1))) {
Cbuf_Error ("access",
"file::delete: access to %s denied", GIB_Argv(1));
return;
}
path = GIB_Argv (1);
if (Qremove(path))
Cbuf_Error ("file",
"file::delete: could not delete %s: %s",
path, strerror(errno));
}
void
GIB_Range_f (void)
{
double i, inc, start, limit;
dstring_t *dstr;
const char *ifs;
if (GIB_Argc () < 3 || GIB_Argc () > 4) {
Cbuf_Error ("syntax",
"range: invalid syntax\n"
"range: lower upper [step]");
return;
}
limit = atof(GIB_Argv(2));
start = atof(GIB_Argv(1));
if (GIB_Argc () == 4)
inc = atof(GIB_Argv(3));
else
inc = limit < start ? -1.0 : 1.0;
if (inc == 0.0) {
GIB_Return ("");
return;
}
if (!(ifs = GIB_Var_Get_Local (cbuf_active, "ifs")))
ifs = " ";
dstr = dstring_newstr ();
for (i = atof(GIB_Argv(1)); inc < 0 ? i >= limit : i <= limit; i += inc)
dasprintf(dstr, "%.1s%.10g", ifs, i);
GIB_Return (dstr->str[0] ? dstr->str+1 : "");
dstring_delete (dstr);
}
void
GIB_Print_f (void)
{
if (GIB_Argc() != 2) {
Cbuf_Error ("syntax",
"print: invalid syntax\n"
"usage: print text");
return;
}
Sys_Printf ("%s", GIB_Argv(1));
}
void
GIB_Builtin_Init (qboolean sandbox)
{
gib_globals = Hash_NewTable (512, GIB_Var_Get_Key, GIB_Var_Free, 0);
if (sandbox)
GIB_File_Transform_Path = GIB_File_Transform_Path_Secure;
else
GIB_File_Transform_Path = GIB_File_Transform_Path_Null;
GIB_Builtin_Add ("function", GIB_Function_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("function::get", GIB_Function_Get_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("function::export", GIB_Function_Export_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("local", GIB_Local_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("global", GIB_Global_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("global::delete", GIB_Global_Delete_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("return", GIB_Return_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("if", GIB_If_f, GIB_BUILTIN_FIRSTONLY);
GIB_Builtin_Add ("ifnot", GIB_If_f, GIB_BUILTIN_FIRSTONLY);
GIB_Builtin_Add ("while", GIB_While_f, GIB_BUILTIN_NOPROCESS);
GIB_Builtin_Add ("field::get", GIB_Field_Get_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("for", GIB_For_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("__for", GIB___For_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("break", GIB_Break_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("string::length", GIB_String_Length_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("string::equal", GIB_String_Equal_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("string::findsub", GIB_String_Findsub_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("regex::match", GIB_Regex_Match_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("regex::replace", GIB_Regex_Replace_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("regex::extract", GIB_Regex_Extract_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("thread::create", GIB_Thread_Create_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("thread::kill", GIB_Thread_Kill_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("file::read", GIB_File_Read_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("file::write", GIB_File_Write_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("file::find", GIB_File_Find_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("file::move", GIB_File_Move_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("file::delete", GIB_File_Delete_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("range", GIB_Range_f, GIB_BUILTIN_NORMAL);
GIB_Builtin_Add ("print", GIB_Print_f, GIB_BUILTIN_NORMAL);
}