/*
CVSNT Default trigger handler
Copyright (C) 2004 Tony Hoyle and March-Hare Software Ltd
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <process.h>
#include <winsock2.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_DIRECT_H
#include <direct.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#define MODULE info
#include <ctype.h>
#include <cvstools.h>
#include <map>
#include "../version.h"
#define CVSROOT_COMMITINFO "CVSROOT/commitinfo"
#define CVSROOT_HISTORYINFO "CVSROOT/historyinfo"
#define CVSROOT_LOGINFO "CVSROOT/loginfo"
#define CVSROOT_NOTIFY "CVSROOT/notify"
#define CVSROOT_POSTCOMMAND "CVSROOT/postcommand"
#define CVSROOT_POSTCOMMIT "CVSROOT/postcommit"
#define CVSROOT_POSTMODULE "CVSROOT/postmodule"
#define CVSROOT_PRECOMMAND "CVSROOT/precommand"
#define CVSROOT_PREMODULE "CVSROOT/premodule"
#define CVSROOT_RCSINFO "CVSROOT/rcsinfo"
#define CVSROOT_TAGINFO "CVSROOT/taginfo"
#define CVSROOT_VERIFYMSG "CVSROOT/verifymsg"
#define CVSROOT_KEYWORDS "CVSROOT/keywords"
struct options
{
char command;
const char **data;
int (*fn)(int num, const char **reslt, void *param);
void *param;
};
int parse_info(const char *file, const char *default_cmd_parms, const char *default_io_parms, const char *directory, options *generic_options, options *spec_options);
int parse_rcsinfo(const char *file, const char *directory, cvs::string& rcsline);
int parse_keywords(const char *file, const char *directory, const char *keyword, options *generic_options, options *spec_options, cvs::string& keywordline, bool have_locker);
int parse_info_line(std::vector<cvs::string>&cache, const char *line, options *generic_options, options *spec_options, const char *source_file, size_t& source_line);
int __parse_info_line(const char *line, options *generic_options, options *spec_options, const char *source_file, size_t& source_line, const char **here_text, cvs::string* io, cvs::string& out, bool no_escape);
cvs::string& auto_escape(const char *_str, char quote);
typedef std::map<const char *,const char *> uservar_t;
cvs::string g_io;
size_t g_ioPos;
static const property_info *prop_info = NULL;
static size_t prop_count = 0;
struct generic_information
{
const char *command; // %c - command being executed
const char *date; // %d - date/time of action
const char *hostname; // %h - remote host name
const char *username; // %u - cvs username
const char *virtual_repository; // %r - repository name (virtual)
const char *physical_repository; // %R - repository name (physical)
const char *sessionid; // %S - session id
const char *editor; // %e - editor
const char *local_hostname; // %H - local host name
const char *local_directory; // %P - local directory
const char *client_version; // %i - client version
const char *character_set; // %x - character set
// Also %n - empty string
// %% - %
uservar_t uservar; // User variables
const char *pid;
};
generic_information gen_info;
options generic_options[] =
{
{ 'c', &gen_info.command },
{ 'd', &gen_info.date },
{ 'h', &gen_info.hostname },
{ 'u', &gen_info.username },
{ 'r', &gen_info.virtual_repository },
{ 'R', &gen_info.physical_repository },
{ 'S', &gen_info.sessionid },
{ 'e', &gen_info.editor },
{ 'H', &gen_info.local_hostname },
{ 'P', &gen_info.local_directory },
{ 'i', &gen_info.client_version },
{ 'x', &gen_info.character_set },
{ 0 }
};
struct _env_info
{
const char *env;
const char **val;
} env_info[] =
{
{ "CVSEDITOR", &gen_info.editor },
{ "VISUAL", &gen_info.editor },
{ "EDITOR", &gen_info.editor },
{ "USER", &gen_info.username },
{ "CVSPID", &gen_info.pid },
{ "SESSIONID", &gen_info.sessionid },
{ "COMMITID", &gen_info.sessionid },
{ "CVSROOT", &gen_info.virtual_repository },
{ "REAL_CVSROOT", &gen_info.physical_repository },
{ "VIRTUAL_CVSROOT", &gen_info.virtual_repository },
{ 0 }
};
int generic_get_char(int num, const char **reslt, void *param)
{
static char ch[2]={ *(char*)param, 0 };
if(num==-1) return 0;
*reslt=ch;
return 0;
}
//commitinfo
//
// %r/%p %<s
//
struct precommit_information
{
int name_list_count;
const char **name_list; // %s - file name
const char *message; // %m - message supplied on command line
const char *directory; // %p - directory name
};
precommit_information precmt_info;
int prec_enum_name_list(int num, const char **reslt, void *param)
{
precommit_information *info = (precommit_information*)param;
if(num==-1) return 0;
if(num>=info->name_list_count)
{
*reslt=NULL;
return 0;
}
*reslt=info->name_list[num];
return num+1<info->name_list_count;
}
options precommit_options[] =
{
{ 'm', &precmt_info.message },
{ 'p', &precmt_info.directory },
{ 's', NULL, prec_enum_name_list, &precmt_info },
{ 0 }
};
//taginfo
//
// %t %o %r/%p %<{%s %v}
//
struct pretag_information
{
const char *message; // %m - message supplied on command line
const char *directory; // %p - directory name
int name_list_count;
const char **name_list; // %s - file name
const char **version_list; // %v - new version
char tag_type; // %b - tag type (T,N,?)
const char *action; // %o - operation (add,mov,del)
const char *tag; // %t - tag name
};
pretag_information pret_info;
int pret_enum_name_list(int num, const char **reslt, void *param)
{
pretag_information *info = (pretag_information*)param;
if(num==-1) return 0;
if(num>=info->name_list_count)
{
*reslt=NULL;
return 0;
}
*reslt=info->name_list[num];
return num+1<info->name_list_count;
}
int pret_enum_version_list(int num, const char **reslt, void *param)
{
pretag_information *info = (pretag_information*)param;
if(num==-1) return 0;
if(num>=info->name_list_count)
{
*reslt=NULL;
return 0;
}
*reslt=info->version_list[num];
return num+1<info->name_list_count;
}
options pretag_options[] =
{
{ 'm', &pret_info.message },
{ 'p', &pret_info.directory },
{ 's', NULL, pret_enum_name_list, &pret_info },
{ 'v', NULL, pret_enum_version_list, &pret_info },
{ 'b', NULL, generic_get_char, &pret_info.tag_type },
{ 'o', &pret_info.action },
{ 't', &pret_info.tag },
{ 0 }
};
//verifymsg
//
// %l
//
struct verifymsg_information
{
const char *directory; // %p - directory name
const char *filename; // %l - path to file containing log message
};
verifymsg_information verif_info;
options verifymsg_options[] =
{
{ 'p', &verif_info.directory },
{ 'l', &verif_info.filename },
{ 0 }
};
//loginfo
//
// %<<stuff (predefined) stuff
//
struct loginfo_information
{
const char *message; // %m - message supplied on command line
const char *status; // %T - status string
const char *directory; // %p - directory name
int change_list_count;
bool rep_flag;
change_info *change_list; // %s, %V, %v, %b, %t, %y
};
loginfo_information login_info;
int login_enum_filename(int num, const char **reslt, void *param)
{
loginfo_information *info = (loginfo_information*)param;
if(num==-1)
{
info->rep_flag = false;
return 0;
}
if(!info->rep_flag)
{
*reslt=info->directory;
info->rep_flag=true;
return 2; /* Try again */
}
if(num>=info->change_list_count)
{
*reslt=NULL;
return 0;
}
*reslt=info->change_list[num].filename;
if(info->change_list[num].type=='T')
return 5;
else
return num+1<info->change_list_count;
}
int login_enum_oldrev(int num, const char **reslt, void *param)
{
loginfo_information *info = (loginfo_information*)param;
if(num==-1) return 0;
if(num>=info->change_list_count)
{
*reslt=NULL;
return 0;
}
if(info->change_list[num].type=='T')
return 4;
*reslt=info->change_list[num].rev_old;
if(!*reslt)
*reslt="NONE";
return num+1<info->change_list_count;
}
int login_enum_newrev(int num, const char **reslt, void *param)
{
loginfo_information *info = (loginfo_information*)param;
if(num==-1) return 0;
if(num>=info->change_list_count)
{
*reslt=NULL;
return 0;
}
if(info->change_list[num].type=='T')
return 4;
*reslt=info->change_list[num].rev_new;
if(!*reslt)
*reslt="NONE";
return num+1<info->change_list_count;
}
int login_enum_bugid(int num, const char **reslt, void *param)
{
loginfo_information *info = (loginfo_information*)param;
if(num==-1) return 0;
if(num>=info->change_list_count)
{
*reslt=NULL;
return 0;
}
*reslt=info->change_list[num].bugid;
return num+1<info->change_list_count;
}
int login_enum_tag(int num, const char **reslt, void *param)
{
loginfo_information *info = (loginfo_information*)param;
if(num==-1) return 0;
if(num>=info->change_list_count)
{
*reslt=NULL;
return 0;
}
*reslt=info->change_list[num].tag;
return num+1<info->change_list_count;
}
int login_enum_type(int num, const char **reslt, void *param)
{
static char ch[2]={0};
loginfo_information *info = (loginfo_information*)param;
if(num==-1) return 0;
if(num>=info->change_list_count)
{
*reslt=NULL;
return 0;
}
ch[0]=info->change_list[num].type;
*reslt=ch;
return num+1<info->change_list_count;
}
options loginfo_options[] =
{
{ 'm', &login_info.message },
{ 'T', &login_info.status },
{ 'p', &login_info.directory },
{ 's', NULL, login_enum_filename, &login_info },
{ 'V', NULL, login_enum_oldrev, &login_info },
{ 'v', NULL, login_enum_newrev, &login_info },
{ 'b', NULL, login_enum_bugid, &login_info },
{ 't', NULL, login_enum_tag, &login_info },
{ 'y', NULL, login_enum_type, &login_info },
{ 0 }
};
// history
// %t|%d|%u|%w|%s|%v
//
struct history_information
{
char type; // %t - history type
const char *workdir; // %w - working dir
const char *revs; // %v - revisions
const char *name; // %s - name
const char *bugid; // %b - bugid
const char *message; // %m - message
};
history_information hist_info;
options history_options[] =
{
{ 't', NULL, generic_get_char, &hist_info.type },
{ 'w', &hist_info.workdir },
{ 'v', &hist_info.revs },
{ 's', &hist_info.name },
{ 'b', &hist_info.bugid },
{ 'm', &hist_info.message },
{ 0 }
};
//precommand
//
// %r %c %<a
//
struct precommand_information
{
int argc;
const char **argv; // %a - command arguments
};
precommand_information prec_info;
int prec_enum_arguments(int num, const char **reslt, void *param)
{
precommand_information *info = (precommand_information*)param;
if(num==-1) return 0;
if(num>=info->argc)
{
*reslt=NULL;
return 0;
}
*reslt=info->argv[num];
return num+1<info->argc;
}
options precommand_options[] =
{
{ 'a', NULL, prec_enum_arguments, &prec_info },
{ 0 }
};
//premodule
//
// %r/%p %c %o
//
struct premodule_information
{
const char *module; // %o - module name
};
premodule_information prem_info;
options premodule_options[] =
{
{ 'o', &prem_info.module},
{ 0 }
};
//postcommand
//
// %r/%p %c
//
struct postcommand_information
{
const char *directory; // %p - directory name
};
postcommand_information postcmd_info;
options postcommand_options[] =
{
{ 'p', &postcmd_info.directory },
{ 0 }
};
//postcommit
//
// %r/%p
//
struct postcommit_information
{
const char *directory; // %p - directory name
};
postcommit_information postcmt_info;
options postcommit_options[] =
{
{ 'p', &postcmt_info.directory },
{ 0 }
};
//postmodule
//
// %r/%p %c %o
//
struct postmodule_information
{
const char *module; // %o - module name
};
postmodule_information postm_info;
options postmodule_options[] =
{
{ 'o', &postm_info.module},
{ 0 }
};
//notify
//
// %<< [predefined]
struct notify_information
{
const char *message; // %m - message supplied on command line
const char *bugid; // %b - bug identifier
const char *directory; // %p - directory name
const char *notify_user; // %s - user being notified
const char *tag; // %t - tag or branch being edited
const char *type; // %y - notification type
const char *file; // %f - file being notified
};
notify_information notif_info;
options notify_options[] =
{
{ 'm', ¬if_info.message },
{ 'b', ¬if_info.bugid },
{ 'p', ¬if_info.directory },
{ 's', ¬if_info.notify_user },
{ 't', ¬if_info.tag },
{ 'y', ¬if_info.type },
{ 'f', ¬if_info.file },
{ 0 }
};
//keywords
//
// Author %a
// Date %d
// Header %r/%p/%f %v %d %a %s %l
// CVSHeader %p/%f %v %d %a %s %l
// Id %f %v %d %a %s %l
// Locker %l
// Log %f
// Name %N
// RCSfile %f
// Revision %v
// Source %r/%p/%f
// State %s
// CommitId %C
struct keyword_information
{
const char *directory; // %p - relative path
const char *file; // %f - file
const char *author; // %a - author
const char *printable_date; // %d - date
const char *rcs_date; // %D - rcs date
const char *locker; // %l - locker
const char *state; // %s - state
const char *version; // %v - version
const char *name; // %N - name
const char *bugid; // %b - bugid
const char *commitid; // %C - commitid
const char *branch; // %t - branch
};
keyword_information keyword_info;
options keyword_options[] =
{
{ 'p', &keyword_info.directory },
{ 'f', &keyword_info.file },
{ 'a', &keyword_info.author },
{ 'd', &keyword_info.printable_date },
{ 'D', &keyword_info.rcs_date },
{ 'l', &keyword_info.locker },
{ 's', &keyword_info.state },
{ 'v', &keyword_info.version },
{ 'N', &keyword_info.name },
{ 'b', &keyword_info.bugid },
{ 'C', &keyword_info.commitid },
{ 't', &keyword_info.branch }
};
int initinfo(const struct trigger_interface_t* cb, const char *command, const char *date, const char *hostname, const char *username, const char *virtual_repository, const char *physical_repository, const char *sessionid, const char *editor, int count_uservar, const char **uservar, const char **userval, const char *client_version, const char *character_set)
{
gen_info.command=command;
gen_info.date=date;
gen_info.hostname=hostname;
gen_info.username=username;
gen_info.virtual_repository=virtual_repository;
gen_info.physical_repository=physical_repository;
gen_info.sessionid=sessionid;
gen_info.editor=editor;
gen_info.client_version=client_version;
gen_info.character_set=character_set;
for(int n=0; n<count_uservar; n++)
gen_info.uservar[uservar[n]]=userval[n];
static char pid[32];
gen_info.pid=pid;
sprintf(pid,"%08x",getpid());
static char host[256];
gethostname(host,sizeof(host));
gen_info.local_hostname=host;
static char cwd[PATH_MAX];
getcwd(cwd,sizeof(cwd));
gen_info.local_directory=cwd;
return 0;
}
int closeinfo(const struct trigger_interface_t* cb)
{
return 0;
}
int pretaginfo(const struct trigger_interface_t* cb, const char *message, const char *directory, int name_list_count, const char **name_list, const char **version_list, char tag_type, const char *action, const char *tag)
{
pret_info.message=message;
pret_info.directory=directory;
pret_info.tag_type=tag_type;
pret_info.action=action;
pret_info.tag=tag;
pret_info.name_list_count=name_list_count;
pret_info.name_list=name_list;
pret_info.version_list=version_list;
return parse_info(CVSROOT_TAGINFO,"%t %o %r/%p","%<{s v}",directory,generic_options,pretag_options);
}
int verifymsginfo(const struct trigger_interface_t* cb, const char *directory, const char *filename)
{
verif_info.directory=directory;
verif_info.filename=filename;
return parse_info(CVSROOT_VERIFYMSG,"%l","",directory,generic_options,verifymsg_options);
}
static void loginfo_filesub(cvs::string& msg, const char *header, char type, int change_list_count, change_info_t *change_list)
{
cvs::string line;
std::map<cvs::string,int> tagList;
bool header_done = false;
for(int n=0; n<change_list_count; n++)
tagList[change_list[n].tag?change_list[n].tag:"\xff"]++;
for(std::map<cvs::string,int>::const_iterator i = tagList.begin(); i!=tagList.end(); ++i)
{
line="";
if(i->first=="\xff" && tagList.size()>1)
line+=" No tag\\n";
else if(i->first!="\xff")
line+=" Tag: "+i->first+"\\n";
line+="\\t";
for(int n=0; n<change_list_count; n++)
{
if(change_list[n].type!=type)
continue;
if(i->first!=(change_list[n].tag?change_list[n].tag:"\xff"))
continue;
if(!header_done)
{
msg+=header;
header_done=true;
}
if(line.length()>1 && line.length()+8+strlen(change_list[n].filename)>72)
{
line+="\\n";
msg+=line;
line="\\t";
}
line+=change_list[n].filename;
line+=' ';
}
if(header_done)
{
line+="\\n";
msg+=line;
}
}
}
int loginfoinfo(const struct trigger_interface_t* cb, const char *message, const char *status, const char *directory, int change_list_count, change_info_t *change_list)
{
cvs::string msg,line;
login_info.message=message;
login_info.status=status;
login_info.directory=directory;
login_info.change_list_count=change_list_count;
login_info.change_list=change_list;
msg="%<< Update of %r/%p\\nIn directory %H:%P\\n\\n";
if(change_list_count)
{
loginfo_filesub(msg,"Modified Files:\\n",'M',change_list_count,change_list);
loginfo_filesub(msg,"Added Files:\\n",'A',change_list_count,change_list);
loginfo_filesub(msg,"Removed Files:\\n",'R',change_list_count,change_list);
}
msg+="Log Message:\\n%m";
if(!*message || message[strlen(message)-1]!='\n')
msg+='\n';
if(status && *status)
{
msg+="\\nStatus:\\n%T";
if(status[strlen(status)-1]!='\n')
msg+='\n';
}
return parse_info(CVSROOT_LOGINFO,"",msg.c_str(),directory,generic_options,loginfo_options);
}
int historyinfo(const struct trigger_interface_t* cb, char type, const char *workdir, const char *revs, const char *name, const char *bugid, const char *message)
{
hist_info.type=type;
hist_info.revs=revs;
hist_info.workdir=workdir;
hist_info.name=name;
hist_info.bugid=bugid;
hist_info.message=message;
return parse_info(CVSROOT_HISTORYINFO,"%t|%d|%u|%w|%s|%v","",NULL,generic_options,history_options);
}
int notifyinfo(const struct trigger_interface_t* cb, const char *message, const char *bugid, const char *directory, const char *notify_user, const char *tag, const char *type, const char *file)
{
cvs::string msg;
notif_info.message=message;
notif_info.bugid=bugid;
notif_info.directory=directory;
notif_info.notify_user=notify_user;
notif_info.tag=tag;
notif_info.type=type;
notif_info.file=file;
return parse_info(CVSROOT_NOTIFY,"","%<< %p %f\\n---\\nTriggered %y watch on %r\\nBy %u",directory,generic_options,notify_options);
}
int precommitinfo(const struct trigger_interface_t* cb, int name_list_count, const char **name_list, const char *message, const char *directory)
{
precmt_info.message=message;
precmt_info.directory=directory;
precmt_info.name_list=name_list;
precmt_info.name_list_count=name_list_count;
return parse_info(CVSROOT_COMMITINFO,"%r/%p","%<s",directory,generic_options,precommit_options);
}
int postcommitinfo(const struct trigger_interface_t* cb, const char *directory)
{
postcmt_info.directory = directory;
return parse_info(CVSROOT_POSTCOMMIT,"%r/%p","",directory,generic_options,postcommit_options);
}
int precommandinfo(const struct trigger_interface_t* cb, int argc, const char **argv)
{
prec_info.argc=argc;
prec_info.argv=argv;
return parse_info(CVSROOT_PRECOMMAND,"%r %c","%<a",NULL,generic_options,precommand_options);
}
int postcommandinfo(const struct trigger_interface_t* cb, const char *directory)
{
postcmd_info.directory = directory;
return parse_info(CVSROOT_POSTCOMMAND,"%r/%p %c","",directory,generic_options,postcommand_options);
}
int premoduleinfo(const struct trigger_interface_t* cb, const char *module)
{
prem_info.module = module;
return parse_info(CVSROOT_PREMODULE,"%r/%p %c %o","",module,generic_options,premodule_options);
}
int postmoduleinfo(const struct trigger_interface_t* cb, const char *module)
{
postm_info.module = module;
return parse_info(CVSROOT_POSTMODULE,"%r/%p %c %o","",module,generic_options,postmodule_options);
}
int get_templateinfo(const struct trigger_interface_t *cb, const char *directory, const char **template_ptr)
{
if(!template_ptr)
return 0;
static cvs::string temp;
cvs::string file;
temp="";
int ret = parse_rcsinfo(CVSROOT_RCSINFO,directory,file);
CFileAccess fa;
if(file.size() && fa.open(file.c_str(),"rb"))
{
size_t len = (size_t)fa.length();
temp.resize(len);
len = fa.read((void*)temp.data(),len);
temp.resize(len);
fa.close();
}
if(!ret && temp.size())
*template_ptr = temp.c_str();
return ret;
}
int parse_keywordinfo(const struct trigger_interface_t *cb, const char *keyword,const char *directory,const char *file,const char *branch,const char *author,const char *printable_date,const char *rcs_date,const char *locker,const char *state,const char *version,const char *name,const char *bugid, const char *commitid, const property_info *props, size_t numprops, const char **value)
{
if(!value)
return 0;
keyword_info.author=author;
keyword_info.bugid=bugid;
keyword_info.directory=directory;
keyword_info.file=file;
keyword_info.locker=locker;
keyword_info.name=name;
keyword_info.printable_date=printable_date;
keyword_info.rcs_date=rcs_date;
keyword_info.state=state;
keyword_info.version=version;
keyword_info.commitid=commitid;
keyword_info.branch=branch;
prop_info = props;
prop_count = numprops;
static cvs::string temp;
temp="";
int ret = parse_keywords(CVSROOT_KEYWORDS,file,keyword,generic_options,keyword_options,temp,(locker&&*locker)?true:false);
if(!ret && temp.size())
*value=temp.c_str();
prop_info = NULL;
prop_count =0;
return ret;
}
int prercsdiffinfo(const struct trigger_interface_t *cb, const char *file, const char *directory, const char *oldfile, const char *newfile, const char *type, const char *options, const char *oldversion, const char *newversion, unsigned long added, unsigned long removed)
{
/* Not exposed to the commit support API */
return 0;
}
int rcsdiffinfo(const struct trigger_interface_t *cb, const char *file, const char *directory, const char *oldfile, const char *newfile, const char *diff, size_t difflen, const char *type, const char *options, const char *oldversion, const char *newversion, unsigned long added, unsigned long removed)
{
/* Not exposed to the commit support API */
return 0;
}
static int init(const struct plugin_interface *plugin);
static int destroy(const struct plugin_interface *plugin);
static void *get_interface(const struct plugin_interface *plugin, unsigned interface_type, void *param);
static trigger_interface callbacks =
{
{
PLUGIN_INTERFACE_VERSION,
"Generic commit support file handler",CVSNT_PRODUCTVERSION_STRING,NULL,
init,
destroy,
get_interface,
NULL
},
initinfo,
closeinfo,
pretaginfo,
verifymsginfo,
loginfoinfo,
historyinfo,
notifyinfo,
precommitinfo,
postcommitinfo,
precommandinfo,
postcommandinfo,
premoduleinfo,
postmoduleinfo,
get_templateinfo,
parse_keywordinfo,
prercsdiffinfo,
rcsdiffinfo
};
static int init(const struct plugin_interface *plugin)
{
return 0;
}
static int destroy(const struct plugin_interface *plugin)
{
return 0;
}
static void *get_interface(const struct plugin_interface *plugin, unsigned interface_type, void *param)
{
if(interface_type!=pitTrigger)
return NULL;
return (void*)&callbacks;
}
plugin_interface *get_plugin_interface()
{
return &callbacks.plugin;
}
/*
*
* Generic parser.
*
* <match|DEFAULT|ALL> <line>
*
* %x - Option x, possibly repeated
* %{x,y,z} - Option x,y,z, possibly repeated
* %<x - Option x to STDIN, possibly repeated
* %<{x y z} - Option x,y,z to STDIN, possible repeated
* %<< text - text to STDIN
* %<<FOO
*
* .... %x %{%x %y %z}
* FOO - Text to STDIN, with linefeeds
*/
int parse_info(const char *file, const char *default_cmd_parms, const char *default_io_parms, const char *directory, options *generic_options, options *spec_options)
{
int ret = 0;
size_t current_line = 0, default_current_line;
cvs::string str,default_line,here_text;
cvs::wildcard_filename mod(directory?directory:"");
cvs::sprintf(str,512,"%s/%s",gen_info.physical_repository,file);
bool found = false;
const char *p;
static std::map<cvs::filename,bool> meta_cache_valid;
static std::map<cvs::filename,std::vector<cvs::string> > meta_cache;
bool& cache_valid = meta_cache_valid[file];
std::vector<cvs::string>& cache = meta_cache[file];
CServerIo::trace(3,"default_trigger: parse_info(%s,%s,%s,%s)",file,default_cmd_parms,default_io_parms,directory?directory:"<null>");
if(!cache_valid)
{
cvs::string line;
CFileAccess acc;
if(!acc.open(str.c_str(),"rb")) /* We have to open as binary, otherwise Win32 breaks... ftell() starts going negative!! */
{
CServerIo::trace(3,"default_trigger: no file");
cache_valid = true;
return 0;
}
while(acc.getline(line))
{
if(!line.length() && line[0]=='#')
line.resize(0);
cache.push_back(line);
}
acc.close();
cache_valid = true;
}
for(current_line=0; current_line<cache.size(); current_line++)
{
cvs::string line;
if(!cache[current_line].length() || cache[current_line][0]=='#')
continue;
line = cache[current_line];
if(here_text.length())
{
if(here_text!=line)
continue;
here_text="";
continue;
}
if((p=strstr(line.c_str(),"%<"))!=NULL)
{
p+=3;
if(!isspace(*p))
here_text=p;
}
if(!strchr(line.c_str(),'%') && default_cmd_parms)
{
line+=" ";
line+=default_cmd_parms;
}
if(!strstr(line.c_str(),"%<") && default_io_parms)
{
line+=" ";
line+=default_io_parms;
}
CTokenLine tok;
const char *trailer;
tok.addArgs(line.c_str(),1,&trailer);
while(*trailer && isspace(*trailer))
trailer++;
CServerIo::trace(3,"Regexp match: %s - %s",tok[0],directory?directory:"");
const char *tk = tok[0];
bool add = false;
if(tk[0]=='+')
{
add=true;
tk++;
}
if(strcmp(tok[0],"ALL")==0)
{
CServerIo::trace(3, "ALL found");
ret += parse_info_line(cache,trailer,generic_options,spec_options,file,current_line);
here_text="";
}
else if((!found || add) && mod.matches_regexp(tk)) // Wildcard match
{
CServerIo::trace(3,"Match found!");
ret += parse_info_line(cache,trailer,generic_options,spec_options,file,current_line);
here_text="";
found=true;
}
else if(!strcmp(tok[0],"DEFAULT"))
{
default_current_line = current_line;
default_line=trailer;
}
}
if(!found && default_line.size())
{
ret += parse_info_line(cache,default_line.c_str(),generic_options,spec_options,file,default_current_line);
}
return ret;
}
int parse_rcsinfo(const char *file, const char *directory, cvs::string& rcsline)
{
int ret = 0;
size_t current_line, default_current_line;
cvs::string str,default_line,here_text;
cvs::wildcard_filename mod(directory?directory:"");
cvs::sprintf(str,512,"%s/%s",gen_info.physical_repository,file);
bool found = false;
static bool cache_valid = false;
static std::vector<cvs::string> cache;
CServerIo::trace(3,"default_trigger: parse_rcsinfo(%s,%s)",file,directory?directory:"<null>");
if(!cache_valid)
{
cvs::string line;
CFileAccess acc;
if(!acc.open(str.c_str(),"rb")) /* We have to open as binary, otherwise Win32 breaks... ftell() starts going negative!! */
{
CServerIo::trace(3,"default_trigger: no file");
cache_valid = true;
return 0;
}
while(acc.getline(line))
{
if(!line.length() && line[0]=='#')
line.resize(0);
cache.push_back(line);
}
acc.close();
cache_valid = true;
}
for(current_line=0; !found && current_line<cache.size(); current_line++)
{
cvs::string line;
if(!cache[current_line].length() || cache[current_line][0]=='#')
continue;
line = cache[current_line];
CTokenLine tok;
const char *trailer;
tok.addArgs(line.c_str(),1,&trailer);
while(*trailer && isspace(*trailer))
trailer++;
CServerIo::trace(3,"Regexp match: %s - %s",tok[0],directory?directory:"");
if(mod.matches_regexp(tok[0])) // Wildcard match
{
found=true;
CServerIo::trace(3,"Match found!");
rcsline=trailer;
}
else if(!strcmp(tok[0],"DEFAULT"))
{
default_current_line = current_line;
default_line=trailer;
}
}
if(!found && default_line.size())
rcsline=default_line;
return ret;
}
int parse_keywords(const char *file, const char *directory, const char *keyword, options *generic_options, options *spec_options, cvs::string& keywordline, bool have_locker)
{
int ret = 0;
size_t current_line,found_line=0;
cvs::string str;
cvs::wildcard_filename mod(directory?directory:"");
cvs::sprintf(str,512,"%s/%s",gen_info.physical_repository,file);
bool insection = false,found = false,all = false,all_found=false;
static bool cache_valid = false;
static std::vector<cvs::string> cache;
CServerIo::trace(3,"default_trigger: parse_keywords(%s,%s)",file,directory?directory:"<null>");
if(!cache_valid)
{
cvs::string line;
CFileAccess acc;
if(!acc.open(str.c_str(),"rb")) /* We have to open as binary, otherwise Win32 breaks... ftell() starts going negative!! */
{
CServerIo::trace(3,"default_trigger: no file");
}
else
{
while(acc.getline(line))
{
if(!line.length() && line[0]=='#')
line.resize(0);
cache.push_back(line);
}
acc.close();
}
cache_valid = true;
}
str="";
for(current_line=0; !found && current_line<cache.size(); current_line++)
{
cvs::string line;
if(!cache[current_line].length() || cache[current_line][0]=='#')
continue;
line = cache[current_line];
CTokenLine tok;
const char *trailer;
tok.addArgs(line.c_str(),1,&trailer);
while(*trailer && isspace(*trailer))
trailer++;
if(isspace((unsigned char)line[0]))
{
if(!insection || strcmp(tok[0],keyword))
continue;
str = trailer;
found_line = current_line;
if(!all)
found=true;
else
all_found=true;
continue;
}
if(insection)
insection=false;
CServerIo::trace(3,"Regexp match: %s - %s",tok[0],directory?directory:"");
if(mod.matches_regexp(tok[0])) // Wildcard match
{
insection=true;
all=false;
CServerIo::trace(3,"Match found!");
}
else if(!strcmp(tok[0],"ALL"))
{
insection=true;
all=true;
CServerIo::trace(3,"ALL section found");
}
}
found|=all_found;
if(!found)
{
if(!strcmp(keyword,"Author")) str="%a";
else if(!strcmp(keyword,"Date")) str="%d";
else if(!strcmp(keyword,"Header")) { if(have_locker) str="%r/%p/%f %v %d %a %s %l"; else str="%r/%p/%f %v %d %a %s"; }
else if(!strcmp(keyword,"CVSHeader")) { if(have_locker) str="%p/%f %v %d %a %s %l"; else str="%p/%f %v %d %a %s"; }
else if(!strcmp(keyword,"Id")) { if(have_locker) str="%f %v %d %a %s %l"; else str="%f %v %d %a %s"; }
else if(!strcmp(keyword,"Locker")) str="%l";
else if(!strcmp(keyword,"Log")) str="%f";
else if(!strcmp(keyword,"Name")) str="%N";
else if(!strcmp(keyword,"RCSfile")) str="%f";
else if(!strcmp(keyword,"Revision")) str="%v";
else if(!strcmp(keyword,"Source")) str="%r/%p/%f";
else if(!strcmp(keyword,"State")) str="%s";
else if(!strcmp(keyword,"CommitId")) str="%C";
else if(!strcmp(keyword,"Branch")) str="%t";
found=true;
}
if(found)
{
__parse_info_line(str.c_str(), generic_options, spec_options, file, found_line, NULL, NULL, keywordline, true);
}
return ret;
}
int parse_output(const char *str, size_t len, void *)
{
return CServerIo::output(len,str);
}
int parse_error(const char *str, size_t len, void *)
{
return CServerIo::error(len,str);
}
int parse_input(char *str, size_t len, void *)
{
const char *s = g_io.c_str();
if(g_ioPos>=g_io.size())
return -1;
size_t l = g_io.size()-g_ioPos;
if(l>len) l=len;
memcpy(str,s+g_ioPos,l);
g_ioPos+=l;
return l;
}
int parse_info_line(std::vector<cvs::string>&cache, const char *line, options *generic_options, options *spec_options, const char *source_file, size_t& source_line)
{
int state;
const char *here_text = NULL;
cvs::string io,out;
CServerIo::trace(3,"parse_info_line: Line=%s",line);
state = __parse_info_line(line,generic_options,spec_options,source_file,source_line,&here_text,&io,out,false);
if(state==3)
{
cvs::string l,o;
/* Multiline text */
do
{
source_line++;
cvs::string& l = cache[source_line];
if(source_line>=cache.size())
{
CServerIo::error("Unterminated multiline expansion at line %d of %s\n",source_line,source_file);
return 1;
}
if(!strcmp(l.c_str(),here_text))
break;
/* Parse the line...*/
o="";
state = __parse_info_line(l.c_str(),generic_options,spec_options,source_file,source_line,NULL,NULL,o,false);
if(state<0)
return 1;
io+=o+'\n';
} while(1);
}
CRunFile rf;
CServerIo::trace(3,"Run arguments: %s",out.c_str());
rf.setArgs(out.c_str());
if(io.size())
rf.setInput(parse_input,NULL);
rf.setOutput(parse_output,NULL);
rf.setError(parse_error,NULL);
g_io = io;
g_ioPos=0;
if(!rf.run(NULL))
{
CServerIo::warning("Script execution failed\n");
return -1;
}
else
{
int ret;
rf.wait(ret);
return ret;
}
}
int __parse_info_line(const char *line, options *generic_options, options *spec_options, const char *source_file, size_t& source_line, const char **here_text, cvs::string* io, cvs::string& out, bool no_escape)
{
int state = 0, group_count = 0;
const char *grouping = NULL;
int stdio_state = 0;
int more_todo = 0;
bool spacer=false, bracket=false;
cvs::string cur,tmp;
options *o;
const char *q;
int l;
cur.reserve(256);
out.reserve(out.size()+strlen(line)+256);
if(io)
io->reserve(io->size()+strlen(line)+256);
for(const char *p=line; *p; p++)
{
switch(state)
{
case 0:
if(grouping)
{
CServerIo::error("Internal error: reached state 0 within group in line %d of %s\n",source_line,source_file);
return -1;
}
switch(*p)
{
case '%':
state=2; break;
case '\\':
state=1; break;
case '$':
state=4; break;
default:
cur+=*p;
if(stdio_state==1)
stdio_state=0;
break;
}
break;
case 1: // \x
if(grouping)
{
CServerIo::error("Internal error: reached state 1 within group in line %d of %s\n",source_line,source_file);
return -1;
}
switch(*p)
{
case 'n':
cur+='\n';
break;
case 'r':
cur+='\r';
break;
case 'b':
cur+='\b';
break;
case 't':
cur+='\t';
break;
default:
if(isspace(*p) || *p=='%' || *p=='$' || *p==',' || *p=='{' || *p=='}' || *p=='<' || *p=='>' || *p=='\\' || *p=='\'' || *p=='"')
cur+=*p;
else
{
CServerIo::warning("Unknown escape character '\\%c' ignored.\n",*p);
cur+='\\';
cur+=*p;
}
break;
}
if(stdio_state==1)
stdio_state=0;
state=0;
break;
case 2: // %
switch(*p)
{
case 'n':
state=grouping?2:0;
spacer=true;
break;
case ' ':
case ',':
case '%':
spacer=true;
cur+=*p;
if(!grouping)
{
if(stdio_state==1)
stdio_state=0;
}
state=grouping?2:0;
break;
case '{':
if(grouping)
{
CServerIo::error("Invalid character '{' in line %d of %s\n",source_line,source_file);
return -1;
}
grouping=p+1;
more_todo = 0;
group_count = -1;
spacer=true;
state=2;
break;
case '}':
if(!grouping)
{
CServerIo::error("Invalid character '}' in line %d of %s\n",source_line,source_file);
return -1;
}
if(stdio_state)
{
(*io)+=cur;
(*io)+='\n';
}
if(more_todo)
{
if(!stdio_state)
cur+=' ';
else
cur="";
p = grouping-1;
state = 2;
if(group_count==-1) group_count++;
if(!(more_todo&2))
group_count++;
spacer=true;
more_todo = 0;
}
else
{
if(!stdio_state)
{
if(no_escape)
out+=cur.c_str();
else
out+=auto_escape(cur.c_str(),'"');
}
cur="";
if(stdio_state==1)
stdio_state=0;
grouping = NULL;
state = 0;
}
break;
case '<':
if(!grouping)
{
state=3;
break;
}
/* Fall through */
default:
if(grouping)
{
if(!spacer)
cur+=',';
spacer = false;
}
/* process option lists */
bool opt_found = false;
for(size_t pass=0; pass<2 && !opt_found; pass++)
{
switch(pass)
{
case 0: o = spec_options; break;
case 1: o = generic_options; break;
}
for(;o && o->command; o++)
{
if(o->command==*p)
{
opt_found = true;
if(!o->fn)
{
if(*o->data)
{
if(grouping && !no_escape)
cur+=auto_escape(*o->data,'\\');
else if(!stdio_state && !no_escape)
cur+=auto_escape(*o->data,'"');
else
cur+=*o->data;
}
}
else
{
const char *d = NULL;
if(!grouping)
{
group_count=0;
o->fn(-1,NULL,o->param);
do
{
more_todo = o->fn(group_count,&d,o->param);
if(d)
{
if(stdio_state)
{
cur+=d;
cur+='\n';
}
else
{
if((more_todo || group_count) && !no_escape)
cur+=auto_escape(d,'\\');
else
cur+=d;
if(more_todo)
cur+=' ';
}
if(more_todo&1)
group_count++;
d=NULL;
}
else if(more_todo)
group_count++;
} while(more_todo);
if(!stdio_state && !no_escape)
cur=auto_escape(cur.c_str(),'"');
}
else
{
if(group_count==-1)
{
o->fn(-1,NULL,o->param);
more_todo |= o->fn(0,&d,o->param);
}
else
more_todo |= o->fn(group_count,&d,o->param);
if(d)
{
if(no_escape)
cur+=d;
else
cur+=auto_escape(d,'\\');
}
}
}
break;
}
}
}
if(!opt_found)
CServerIo::warning("Unrecognised expansion '%c' in line %d of %s\n",*p,source_line,source_file);
state=grouping?2:0; break;
}
break;
case 3: // %<<
if(!here_text)
{
CServerIo::error("Invalid < within multiline text at line %d of %s\n",source_line,source_file);
return -1;
}
if(!io)
{
CServerIo::error("%< is not allowed in this file at line %d of %s\n",source_line,source_file);
return -1;
}
switch(*p)
{
case '<':
/* here document */
if(!isspace(*(p+1)))
{
*here_text=p+1;
p+=strlen(p);
return state;
}
p++;
state = 0;
stdio_state=2;
break;
default:
p--; /* Step back */
stdio_state=1;
state=2;
break;
}
break;
case 4: /* $var */
if(*p=='{')
{
p++;
q=strchr(p,'}');
if(!q)
{
CServerIo::error("unterminated '{' in line %d of %s\n",source_line,source_file);
return -1;
}
l=q-p;
q+=2;
}
else
{
q=p;
if(*q=='=')
q++;
while(isalnum(*q) || *q=='_')
q++;
l=q-p;
}
bool only_uservar=false;
if(*p=='=')
{
p++;
l--;
only_uservar=true;
}
tmp=p;
p=q-1;
tmp.resize(l);
size_t n = 0;
if(!only_uservar)
{
for(n; env_info[n].env; n++)
{
if(!strcmp(env_info[n].env,tmp.c_str()) && env_info[n].val)
{
if(no_escape)
cur += *env_info[n].val;
else
cur += auto_escape(*env_info[n].val,'\\');
break;
}
}
}
if(only_uservar || !env_info[n].env)
{
uservar_t::const_iterator i = gen_info.uservar.find(tmp.c_str());
bool ff = false;
if(i!=gen_info.uservar.end())
{
if(no_escape)
cur+=gen_info.uservar[tmp.c_str()];
else
cur+=auto_escape(gen_info.uservar[tmp.c_str()],'\\');
ff = true;
}
if(!ff && prop_info && prop_count)
{
for(size_t p=0; p<prop_count; p++)
{
if(!strcmp(prop_info[p].property,tmp.c_str()))
{
if(no_escape)
cur+=prop_info[p].value;
else
cur+=auto_escape(prop_info[p].value,'\\');
ff = true;
break;
}
}
}
if(!only_uservar && !ff)
{
q = CProtocolLibrary::GetEnvironment(tmp.c_str());
if(q)
{
if(no_escape)
cur+=q;
else
cur+=auto_escape(q,'\\');
}
}
}
state=0;
break;
}
if(!grouping && cur.size())
{
if(stdio_state)
(*io)+=cur;
else
{
if(cur=="\\")
out+="\\\\";
else
out+=cur;
}
if(stdio_state==1)
stdio_state=0;
cur="";
}
if(grouping && more_todo&6)
p=strchr(p,'}')-1;
more_todo=more_todo&~4;
}
if(grouping)
{
CServerIo::error("Unterminated group in line %d of %s\n",source_line,source_file);
return -1;
}
return state;
}
cvs::string& auto_escape(const char *_str, char quote)
{
static const char meta[] = "`\"'\\ ";
static cvs::string str;
str=_str;
if(!strpbrk(str.c_str(),meta))
return str;
else
{
str.reserve(str.size()+16);
if(quote!='\\')
{
/* Replace any '\' or quote character within the string with \char */
int i=0;
char bs = '\\';
char mt[3] = ".\\";
mt[0]=quote;
do
{
i=str.find_first_of(mt,i);
if(i==cvs::string::npos)
break;
str.insert(i++,&bs,1);
++i;
} while(1);
str.insert(str.begin(),quote);
str.insert(str.end(),quote);
}
else
{
int i=0;
do
{
i=str.find_first_of(meta,i);
if(i==cvs::string::npos)
break;
str.insert(i++,"e,1);
++i;
} while(1);
}
}
return str;
}
syntax highlighted by Code2HTML, v. 0.9.1