/*
* Copyright 1995, 2000 Perforce Software. All rights reserved.
*
* This file is part of Perforce - the FAST SCM System.
*/
# define NEED_QUOTE
# include <stdhdrs.h>
# include <charman.h>
# include <debug.h>
# include <strbuf.h>
# include <strdict.h>
# include <strtable.h>
# include <strarray.h>
# include <strops.h>
# include <splr.h>
# include <error.h>
# include <options.h>
# include <handler.h>
# include <vararray.h>
# include <rpc.h>
# include <pathsys.h>
# include <filesys.h>
# include <ticket.h>
# include <enviro.h>
# include <i18napi.h>
# include <charcvt.h>
# include <charset.h>
# include <hostenv.h>
# include <errorlog.h>
# include <p4tags.h>
# include <regmatch.h>
# include <msgclient.h>
# include <msgsupp.h>
# include <msghelp.h>
# include "client.h"
# include "clientmerge.h"
# include "clientuser.h"
# include "clientuserdbg.h"
# include "clientusermsh.h"
# include "clientaliases.h"
/*
* This file contains the implementation of the various classes used in the
* command-line client aliases feature:
*
* - ClientAliases
* - ClientCommand
* - CommandAlias
* - CommandPattern
* - CommandTransformation
*
* A single static function (ClientAliases::ProcessAliases) provides the
* overall "hook" used by clientMain to handle aliasing.
*/
int
ClientAliases::ProcessAliases(
int argc,
char **argv,
int &result,
Error *e )
{
// Pseudocode:
// LoadAliases
// if error return 1
// if no aliases return 0
//
// ExpandAliases
// if error return 1
// if no relevant aliases return 0
//
// ProcessExpandedCommand
Enviro env;
HostEnv hostEnv;
ClientAliases clientAliases( argc, argv, &env, &hostEnv, e );
if( e->Test() || clientAliases.HasCharsetError() )
return result = 1;
if( clientAliases.HasAliasesDisabled() )
return 0;
result = 0;
clientAliases.LoadAliases( e );
if( e->Test() )
return result = 1;
if( clientAliases.WasAliasesCommand( result, e ) )
return 1;
if( !clientAliases.HasAliases() )
return result = clientAliases.CheckDryRun( argc, argv, e );
clientAliases.ExpandAliases( argc, argv, e );
if( e->Test() )
return result = 1;
if( !clientAliases.WasExpanded() )
return result = clientAliases.CheckDryRun( argc, argv, e );
result = clientAliases.RunCommands( e );
if( e->Test() )
return result = 1;
return 1;
}
ClientAliases::ClientAliases(
int argc,
char **argv,
Enviro *env,
HostEnv *hostEnv,
Error *e )
{
aliases = new VarArray;
commands = new VarArray;
hasAliases = 0;
hasAliasesDisabled = 0;
hasCharsetError = 0;
wasExpanded = 0;
this->env = env;
this->hostEnv = hostEnv;
aliasesFile = 0;
ioDict = 0;
Client client;
Options opts;
parsedArgc = argc;
parsedArgv = argv;
clientParseOptions( opts, parsedArgc, parsedArgv, e );
if( e->Test() )
return;
if( clientPrepareEnv( client, opts, *(this->env) ) )
{
hasCharsetError = 1;
return;
}
const StrPtr *aliasHandling = opts[ Options::Aliases ];
if( aliasHandling && !strncmp( aliasHandling->Text(), "no", 2 ) )
hasAliasesDisabled = 1;
}
ClientAliases::~ClientAliases()
{
delete ioDict;
if( aliases )
{
for( int i = 0; i < aliases->Count(); i++ )
{
CommandAlias *a = (CommandAlias *)aliases->Get( i );
delete a;
}
delete aliases;
}
if( commands )
{
for( int i = 0; i < commands->Count(); i++ )
{
ClientCommand *a = (ClientCommand *)commands->Get( i );
delete a;
}
delete commands;
}
delete aliasesFile;
}
void
ClientAliases::LoadAliases(
Error *e )
{
// First implementation is very crude, and supports only a single
// aliases file, side by side with your tickets file.
//
// Following (sort of) our other conventions, the file is named
// $HOME/.p4aliases on Unix-y systems, and
// %USERPROFILE%\p4aliases.txt on Windows
hostEnv->GetAliasesFile( aliasesFilePath, env );
aliasesFile = FileSys::Create( (FileSysType)(FST_TEXT|FST_L_LFCRLF) );
aliasesFile->Set( aliasesFilePath );
int stat = aliasesFile->Stat();
if( ( stat & FSF_EXISTS ) )
{
// Ignore ~/.p4aliases if it is a directory:
if( ( stat & FSF_DIRECTORY ) )
return; // FIXME: too quiet?
ReadAliasesFile( e );
if( e->Test() )
return;
}
hasAliases = aliases->Count() > 0;
}
int
ClientAliases::HasAliases()
{
return hasAliases;
}
int
ClientAliases::HasAliasesDisabled()
{
return hasAliasesDisabled;
}
int
ClientAliases::HasCharsetError()
{
return hasCharsetError;
}
void
ClientAliases::ReadAliasesFile(
Error *e )
{
aliasesFile->Open( FOM_READ, e );
if( e->Test() )
return;
StrBuf line;
CommandAlias *alias = new CommandAlias;
while( !e->Test() && aliasesFile->ReadLine( &line, e ) )
{
line.TrimBlanks();
if( IsComment( &line ) )
continue;
alias->Consume( &line );
if( alias->Complete() )
{
alias->Compile( e );
if( !e->Test() )
{
aliases->Put( alias );
alias = new CommandAlias;
}
}
}
if( !alias->Complete() )
e->Set( MsgClient::AliasPartial );
aliasesFile->Close( e );
delete alias;
}
int
ClientAliases::IsComment( StrPtr *line )
{
const char *p = line->Text();
int len = 0;
while( p && *p )
{
if( *p == '#' )
return 1;
if( *p != ' ' && *p != '\t' )
return 0;
len++;
p++;
}
if( len )
return 0;
return 1; // empty line counts as a comment.
}
void
ClientAliases::ShowAliases()
{
for( int i = 0; i < aliases->Count(); i++ )
{
CommandAlias *a = (CommandAlias *)aliases->Get( i );
StrBuf aBuf;
a->Show( aBuf );
printf("%s\n", aBuf.Text());
}
}
void
ClientAliases::ExpandAliases(
int argc,
char **argv,
Error *e )
{
// We iteratively attempt to apply our aliases. Each time we
// successfully apply an alias, we restart the entire process.
//
// We stop when:
// - we encounter an error, or
// - we went all the way through and nothing changed
//
// If we successfully expanded the user's command using our aliases,
// we can then run the results of the expansion
//
// Note that, initially, we are trying to match/transform the user's
// raw command (in argc/argv), but subsequently we are trying to
// match the working command, which is a copy.
//
// And since a single alias may expand to a list of commands, in the
// end we may have multiple commands to run.
ClientCommand *cmd = new ClientCommand;
commands->Put( cmd );
cmd->CopyArguments( argc, argv, e );
restart:
if( e->Test() )
return;
for( int c = 0; c < commands->Count(); c++ )
{
cmd = (ClientCommand *)commands->Get( c );
for( int i = 0; i < aliases->Count(); i++ )
{
CommandAlias *a = (CommandAlias *)aliases->Get( i );
if( a->Matches( cmd ) )
{
VarArray resultCommands;
a->Transform( cmd, &resultCommands, e );
if( e->Test() )
return;
if( resultCommands.Count() > 0 )
{
UpdateCommands( c, &resultCommands );
wasExpanded = 1;
goto restart;
}
}
}
}
}
int
ClientAliases::WasExpanded()
{
return wasExpanded;
}
void
ClientAliases::UpdateCommands( int which, VarArray *resultCommands )
{
ClientCommand *oldOne = (ClientCommand *)commands->Get( which );
VarArray *revisedCommands = new VarArray;
for( int i = 0; i < commands->Count(); i++ )
{
if( i != which )
{
revisedCommands->Put( commands->Get( i ) );
}
else
{
for( int j = 0; j < resultCommands->Count(); j++ )
revisedCommands->Put( resultCommands->Get( j ) );
}
}
delete oldOne;
delete commands;
commands = revisedCommands;
}
int
ClientAliases::RunCommands( Error *e )
{
ioDict = new StrBufDict;
for( int c = 0; !e->Test() && c < commands->Count(); c++ )
{
ClientCommand *cmd = (ClientCommand *)commands->Get( c );
if( cmd->RunCommand( ioDict, e ) )
return 1;
}
return 0;
}
// Returns 1 if this was a dry run, with an appropriate message, else returns 0
//
int
ClientAliases::CheckDryRun(
int argc,
char **argv,
Error *e )
{
Options opts;
int origArgc = argc;
char **origArgv = argv;
clientParseOptions( opts, argc, argv, e );
if( e->Test() )
return 1;
const StrPtr *aliasHandling = opts[ Options::Aliases ];
if( aliasHandling &&
( *aliasHandling == "dry-run" || *aliasHandling == "echo" ) )
{
StrBuf buf;
buf << "p4 ";
int firstArg = 1;
for( int ac = 0; ac < origArgc; ac++ )
{
if( !strncmp(origArgv[ac], "--aliases", 9 ) )
continue;
if( !firstArg )
buf << " ";
buf << origArgv[ac];
firstArg = 0;
}
e->Set( MsgClient::CommandNotAliased ) << buf;
return 1;
}
return 0;
}
int
ClientAliases::WasAliasesCommand(
int &result,
Error *e )
{
if( parsedArgc >= 1 && !strcmp( parsedArgv[0], "aliases" ) )
{
if( parsedArgc >= 2 &&
( !strcmp( parsedArgv[1], "help" ) ||
!strcmp( parsedArgv[1], "-h" ) ||
!strcmp( parsedArgv[1], "-?" ) ||
!strcmp( parsedArgv[1], "?" ) ) )
{
e->Set( MsgHelp::HelpAliases );
return 1;
}
if( !HasAliases() )
{
e->Set( MsgClient::NoAliasesFound );
return result = 1;
}
ShowAliases();
return 1;
}
return 0;
}
/**************************************************************************
* CommandAlias: class for working with a single concrete alias *
**************************************************************************/
CommandAlias::CommandAlias()
{
used = 0;
transforms = new VarArray;
dict = 0;
}
CommandAlias::~CommandAlias()
{
delete dict;
if( transforms )
{
for( int i = 0; i < transforms->Count(); i++ )
{
CommandTransformation *t =
(CommandTransformation *)transforms->Get( i );
delete t;
}
delete transforms;
}
}
void
CommandAlias::Consume( StrPtr *line )
{
text << *line;
}
int
CommandAlias::Complete()
{
// Looking at the end of the alias, we decide whether the alias
// is complete, or whether the next line should be added on to
// this alias. There are two line continuation conventions: && at
// the end of the line, or '\' at the end of the line. The trailing
// backslash, if present, is removed.
text.TrimBlanks();
char *p = text.Text();
while( p && *p && *(p+1) )
{
if( *p == '&' && *(p+1) == '&' && *(p+2) == '\0' )
{
return 0;
}
if( *(p+1) == '\\' && *(p+2) == '\0' )
{
p++;
*p = ' ';
text.SetEnd( p );
text.Terminate();
return 0;
}
p++;
}
return 1;
}
void
CommandAlias::Compile( Error *e )
{
const char *equals = 0;
const char *p = text.Text();
while( p && *p )
{
if( *p == '=' )
{
if( equals )
{
e->Set( MsgClient::AliasTooManyEquals ) << text;
return;
}
equals = p;
}
p++;
}
if( !equals )
{
e->Set( MsgClient::AliasMissingEquals ) << text;
return;
}
if( equals - text.Text() <= 0 )
{
e->Set( MsgClient::AliasEmptyPattern ) << text;
return;
}
pattern.Set( StrRef( text.Text(), equals - text.Text() ) );
pattern.Compile( e );
if( e->Test() )
return;
StrRef xf( equals + 1 );
CompileTransforms( &xf, e );
if( e->Test() )
return;
}
void
CommandAlias::AddTransform( const StrPtr *xf, Error *e)
{
CommandTransformation *t = new CommandTransformation;
transforms->Put( t );
t->Set( xf );
t->Compile( e );
}
void
CommandAlias::CompileTransforms( const StrPtr *xfms, Error *e )
{
// Split apart the multiple commands (if any) at the '&&' points.
const char *p = xfms->Text();
const char *s = p;
while( p && *p )
{
if( p[0] == '&' && p[1] == '&' )
{
StrRef xf( s, p - s );
AddTransform( &xf, e );
if( e->Test() )
return;
p++;
s = p + 1;
}
p++;
}
if( s != p )
{
StrRef xf( s, p - s );
AddTransform( &xf, e );
}
if( e->Test() )
return;
if( !transforms->Count() )
e->Set( MsgClient::AliasNoTransform ) << text;
}
void
CommandAlias::Show( StrBuf &display )
{
pattern.Show( display );
display << " => ";
for( int i = 0; i < transforms->Count(); i++ )
{
CommandTransformation *t =
(CommandTransformation *)transforms->Get( i );
StrBuf msg;
if( i > 0 )
display << " && ";
t->Show( msg );
display << msg;
}
}
int
CommandAlias::Matches( ClientCommand *cmd )
{
return !used && pattern.Matches( cmd );
}
int
CommandAlias::AlreadyUsed()
{
return used;
}
void
CommandAlias::Transform(
ClientCommand *cmd,
VarArray *resultCommands,
Error *e )
{
used = 1;
BuildDictionary( cmd, e );
for( int i = 0; !e->Test() && i < transforms->Count(); i++ )
{
CommandTransformation *t =
(CommandTransformation *)transforms->Get( i );
t->Transform( cmd, i, transforms->Count(),
pattern.Formals(), dict, resultCommands, e );
}
}
void
CommandAlias::BuildDictionary( ClientCommand *cmd, Error *e )
{
// If the alias included any formal arguments, for example:
// recent-by-user $(u) $(m) = changes -u $(u) -m $(m)
// then build up a dictionary providing values for those arguments
// from the actual command arguments.
int n_args = pattern.Formals();
if( n_args > 0 )
{
StrRef var, val;
dict = new StrBufDict();
for( int ac = 0; ac < n_args; ac++ )
{
if( pattern.IsAVariable( ac ) )
{
pattern.GetFormal( ac, &var );
cmd->GetActual( ac, &val );
dict->SetVar( var, val );
}
}
}
}
/**************************************************************************
* CommandPattern: manages the "left hand side" of an alias definition *
**************************************************************************/
CommandPattern::CommandPattern()
{
actual = 0;
maxVec = 0;
vec = 0;
formals = 0;
formalTypes = 0;
}
CommandPattern::~CommandPattern()
{
delete [] vec;
delete [] formals;
delete [] formalTypes;
}
void
CommandPattern::Set( const StrPtr s )
{
text << s;
}
void
CommandPattern::Compile( Error *e )
{
text.TrimBlanks();
maxVec = 100;
vec = new char *[ maxVec ];
actual = StrOps::Words( wordsBuf, text.Text(), vec, maxVec );
if( actual >= maxVec )
e->Set( MsgClient::AliasTooComplex ) << text << StrNum( maxVec );
else if( actual <= 0 )
e->Set( MsgClient::AliasEmptyPattern ) << text;
if( e->Test() )
return;
w0 = vec[0];
if( Formals() > 0 )
ValidateFormals( e );
}
void
CommandPattern::Show( StrBuf &buf )
{
buf << text;
}
int
CommandPattern::Matches( ClientCommand *cmd )
{
return cmd->Matches( this, &w0 );
}
int
CommandPattern::Formals()
{
return actual - 1;
}
void
CommandPattern::ValidateFormals( Error *e )
{
formals = new StrBuf[ Formals() ];
formalTypes = new int[ Formals() ];
// For each formal argument, it should be of the form $(var).
// Extract the variable name and remember it.
for( int ac = 1; ac < actual; ac++ )
{
const char *s = vec[ ac ];
if( s[0] != '$' || s[1] != '(' )
{
formals[ ac - 1 ] = StrRef( s );
formalTypes[ ac - 1 ] = LITERAL;
continue;
}
const char *p = s + 2;
while( p && *p )
{
if( p[0] == ')' )
{
if( p[1] != '\0' )
{
e->Set( MsgClient::AliasArgSyntax )
<< vec[ ac ] << text;
return;
}
formals[ ac - 1 ] = StrRef( s + 2, p - s - 2 );
formalTypes[ ac - 1 ] = VARIABLE;
}
p++;
}
}
}
void
CommandPattern::GetFormal( int ac, StrRef *var )
{
if( ac >= 0 && ac < Formals() )
var->Set( formals[ ac ] );
}
int
CommandPattern::IsAVariable( int ac )
{
if( ac >= 0 && ac < Formals() )
return formalTypes[ ac ] == VARIABLE;
return 0;
}
/***************************************************************************
* CommandTransformation: manages the "right hand side" of an alias *
**************************************************************************/
CommandTransformation::CommandTransformation()
{
opts = new Options;
actual = 0;
maxVec = 0;
vec = 0;
}
CommandTransformation::~CommandTransformation()
{
delete opts;
delete [] vec;
}
void
CommandTransformation::Set( const StrPtr *s )
{
text << s;
}
void
CommandTransformation::Compile( Error *e )
{
text.TrimBlanks();
CompileRedirection( e );
if( e->Test() )
return;
maxVec = 100;
vec = new char *[ maxVec ];
actual = Words( wordsBuf, text.Text(), vec, maxVec );
if( actual >= maxVec )
e->Set( MsgClient::AliasTooComplex ) << text << StrNum( maxVec );
else if( actual <= 0 )
e->Set( MsgClient::AliasEmptyTransform );
if( e->Test() )
return;
// find the fulcrum
argc = actual;
argv = &vec[0];
clientParseOptions( *opts, argc, argv, e );
if( e->Test() )
{
e->Set( MsgClient::AliasSyntaxError ) << text;
return;
}
fulcrum = actual - argc;
if( !argc )
{
e->Set( MsgClient::AliasMissingCommand ) << text;
return;
}
CompileSpecialOperators( e );
if( e->Test() )
return;
}
// Like StrOps::Words, but with slightly different quotes handling
int
CommandTransformation::Words(
StrBuf &tmp,
const char *buf,
char *vec[],
int maxVec )
{
tmp.Clear();
tmp.Alloc( strlen( buf ) + 1 );
tmp.Clear();
int count = 0;
while( count < maxVec )
{
while( isAspace( buf ) ) buf++;
if( !*buf ) break;
vec[ count++ ] = tmp.End();
int quote = 0;
for( ; *buf; ++buf )
{
if( quote && buf[0] == '"' && buf[1] == '"' )
tmp.Extend( buf[0] ), ++buf;
else if( buf[0] == '"' )
quote = !quote;
else if( quote || !isAspace( buf ) )
tmp.Extend( buf[0] );
else break;
}
tmp.Extend( '\0' );
}
return count;
}
void
CommandTransformation::CompileSpecialOperators( Error *e )
{
if( !strcmp( vec[ fulcrum ], "p4subst" ) )
{
// p4subst must have two arguments, and both input and output
// must be redirected.
if( actual != 3 )
e->Set( MsgClient::AliasSubstArgs );
else if( !inputVariable.Length() )
e->Set( MsgClient::AliasSubstInput );
else if( !outputVariable.Length() )
e->Set( MsgClient::AliasSubstOutput );
return;
}
}
void
CommandTransformation::Show( StrBuf &buf )
{
buf << text;
if( inputVariable.Length() )
buf << " < $(" << inputVariable << ")";
if( outputVariable.Length() )
buf << " > $(" << outputVariable << ")";
}
void
CommandTransformation::Transform(
ClientCommand *cmd,
int xformNo,
int numXforms,
int numNamedArgs,
StrDict *namedArgs,
VarArray *resultCommands,
Error *e )
{
ClientCommand *result = new ClientCommand;
if( xformNo == 0 && cmd->GetInput()->Length() )
result->SetInput( cmd->GetInput() );
if( xformNo == numXforms - 1 && cmd->GetOutput()->Length() )
result->SetOutput( cmd->GetOutput() );
if( GetInput()->Length() && !result->GetInput()->Length() )
result->SetInput( GetInput() );
if( GetOutput()->Length() && !result->GetOutput()->Length() )
result->SetOutput( GetOutput() );
cmd->Transform( actual, vec, numNamedArgs, namedArgs, result, e );
if( e->Test() )
{
delete result;
return;
}
resultCommands->Put( result );
}
void
CommandTransformation::CompileRedirection( Error *e )
{
// Look for patterns of the form:
// > $(variable)
// and
// < $(variable)
//
// If we find either or both, excise them from the main body of
// the transform and record them for later use.
int state = 0;
// States:
// 0: Looking for '<' or '>'
// 1: Looking for '$('
// 2: Looking for ')'
const char *p;
const char *pStart; // Location of the < or >
const char *vStart; // Start of the variable name
StrBuf buf;
int redirType = 0; // 0: none; 1: input; 2: output
p = text.Text();
while( p && *p )
{
if( state == 0 )
{
if( p[0] == '<' || p[0] == '>' )
{
pStart = p;
redirType = p[0] == '<' ? 1 : 2;
state = 1;
}
}
else if( state == 1 )
{
if( p[0] == '$' && p[1] == '(' )
{
vStart = p + 2;
state = 2;
p++;
}
else if( p[0] != ' ' )
{
e->Set( MsgClient::AliasIOSyntax )
<< StrRef( p, 1 )
<< text;
return;
}
}
else if( state == 2 )
{
if( p[0] == ')' )
{
if( redirType == 1 )
{
if( inputVariable.Length() > 0 )
{
e->Set( MsgClient::AliasInputMultiple ) << text;
return;
}
inputVariable.Set( vStart, p - vStart );
}
else
{
if( outputVariable.Length() > 0 )
{
e->Set( MsgClient::AliasOutputMultiple ) << text;
return;
}
outputVariable.Set( vStart, p - vStart );
}
buf.Clear();
buf << StrRef( text.Text(), pStart - text.Text() )
<< StrRef( p + 1 );
text.Set( buf );
redirType = 0;
state = 0;
p = text.Text();
continue;
}
}
p++;
}
if( state != 0 )
{
e->Set( MsgClient::AliasRedirection ) << text;
return;
}
text.TrimBlanks();
}
const StrPtr *
CommandTransformation::GetInput()
{
return &inputVariable;
}
const StrPtr *
CommandTransformation::GetOutput()
{
return &outputVariable;
}
/****************************************************************************
* ClientCommand: a single command, in semi-parsed format, with lots of *
* utility functions for manipulating the command. *
****************************************************************************/
ClientCommand::ClientCommand()
{
w_argc = 0;
w_argn = 0;
w_argf = 0;
w_argv = 0;
w_argp = 0;
w_args = 0;
w_opts = 0;
c_args = 0;
ui = 0;
}
ClientCommand::~ClientCommand()
{
delete w_opts;
delete [] w_args;
delete [] w_argp;
delete [] c_args;
delete ui;
}
int
ClientCommand::RunCommand( StrDict *dict, Error *e )
{
// Time to run this command! First, though, finalize any
// variable references, first looking for those that may influence
// the client object itself (-u, -h, -p, etc.), then processing
// those variables that will be arguments to the server command.
Client client;
AliasSubstitution subst( &client, dict );
for( int ac = 0; ac < w_argf; ac++ )
{
subst.Substitute( &w_args[ac] );
w_argp[ac].Set( w_args[ac] );
}
w_argc = w_argn;
ParseOptions( e );
if( e->Test() )
return 1;
clientSetVariables( client, *w_opts );
for( int ac = 0; ac < w_argn; ac++ )
{
subst.Substitute( &w_args[ac] );
if( ac < w_argf )
w_argp[ac].Set( w_args[ac] );
}
// Variable substitutions may have left some of the variables
// with multiple values in the variable, separated by newlines.
// If this particular command has such values, burst them out
// into separate arguments now.
//
SplitArgs( e );
if( e->Test() )
return 1;
const StrPtr *aliasHandling = (*w_opts)[ Options::Aliases ];
if( aliasHandling &&
( *aliasHandling == "dry-run" || *aliasHandling == "echo" ) )
{
StrBuf fmtBuf;
FormatCommand( fmtBuf );
printf("%s\n", fmtBuf.Text() );
if ( *aliasHandling == "dry-run" )
return 0;
}
// We let clientMain do the heavy lifting here, but we typically
// provide a custom ClientUser object so that we can control the
// input and output of the command we run.
PrepareIO( dict, e );
if( e->Test() )
return 1;
int uidebug;
const char *noCommand = "";
char **argv = (char **)&noCommand;
if( w_argc > 0 )
{
c_args = new char *[ w_argc ];
argv = &c_args[0];
}
for( int ac = w_argf; ac < w_argn; ac++ )
c_args[ac - w_argf] = w_args[ac].Text();
if( !HandleSpecialOperators( w_argc, argv, *w_opts, e ) )
clientRunCommand( w_argc, argv, *w_opts, ui, uidebug, e );
HandleOutput( dict, e );
return ui && ui->CommandFailed();
}
void
ClientCommand::HandleOutput( StrDict *dict, Error *e )
{
if( ui )
{
if( dict && outputVariable.Length() )
{
dict->SetVar( outputVariable, *(ui->GetOutput()) );
}
if( !outputVariable.Length() || ui->CommandFailed() )
{
const StrPtr *results = ui->GetOutput();
if( results && results->Text() )
printf( "%s\n", results->Text() );
}
}
}
void
ClientCommand::FormatCommand( StrBuf &b )
{
b << "p4 ";
int firstArg = 1;
for( int ac = 0; ac < w_argn; ac++ )
{
if( w_args[ac] == "--aliases=dry-run" ||
w_args[ac] == "--aliases=echo" )
continue;
if( !firstArg )
b << " ";
b << w_args[ac];
firstArg = 0;
}
if( inputVariable.Length() )
b << " < $(" << inputVariable << ")";
if( outputVariable.Length() )
b << " > $(" << outputVariable << ")";
}
void
ClientCommand::SplitArgs( Error *e )
{
int additionsNeeded = 0;
for( int ac = w_argf; ac < w_argn; ac++ )
{
StrPtrLineReader splr( &w_args[ ac ] );
int linesInArg = splr.CountLines();
if( linesInArg )
additionsNeeded += ( linesInArg - 1 );
}
if( additionsNeeded )
{
int n_argn = w_argn + additionsNeeded;
StrBuf *n_args = new StrBuf[ (n_argn) ];
int dst = 0;
for( int ac = 0; ac < w_argf; ac++ )
n_args[dst++].Set( w_args[ac] );
for( int ac = w_argf; ac < w_argn; ac++ )
{
StrBuf line;
StrPtrLineReader splr( &w_args[ ac ] );
while( splr.GetLine( &line ) )
n_args[dst++].Set( line );
}
UpdateWorkingCommand( n_argn, n_args, e );
}
}
void
ClientCommand::CopyArguments( int cargc, char **argv, Error *e )
{
w_argc = w_argn = cargc;
w_args = new StrBuf[ w_argn ];
w_argp = new StrRef[ w_argn ];
for( int ac = 0; ac < w_argn; ac++ )
{
w_args[ac].Set( StrRef( argv[ac] ) );
w_argp[ac].Set( w_args[ac] );
}
ParseOptions( e );
}
void
ClientCommand::UpdateWorkingCommand( int n_argn, StrBuf *n_args, Error *e )
{
w_argc = w_argn = n_argn;
delete [] w_args;
delete [] w_argp;
w_args = n_args;
w_argp = new StrRef[ w_argn ];
for( int ac = 0; ac < w_argn; ac++ )
w_argp[ac].Set( w_args[ac] );
ParseOptions( e );
}
void
ClientCommand::ParseOptions( Error *e )
{
w_argv = &w_argp[0];
delete w_opts;
w_opts = new Options;
clientParseOptions( *w_opts, w_argc, w_argv, e );
if( !w_argc )
w_argv = 0;
w_argf = w_argn - w_argc;
}
void
ClientCommand::Transform(
int actual,
char **vec,
int numNamedArgs,
StrDict *namedArgs,
ClientCommand *result,
Error *e )
{
// Transformation involves:
// - the replacement of the function with replacement text+options
// - the matching of supplied values to named formal args in the alias
//
// So we first copy everything from before the function, then we
// copy the replacement text, then we copy everything from after
// the function.
//
// Note that we don't transform in place. Rather, we build an entirely
// new result command.
int n_argn = w_argn + ( actual - 1 ) - numNamedArgs;
StrBuf *n_args = new StrBuf[ (n_argn) ];
int dst = 0;
for( int ac = 0; ac < w_argf; ac++ )
n_args[dst++].Set( w_args[ac] );
for( int ac = 0; ac < actual; ac++ )
n_args[dst++].Set( vec[ac] );
if( numNamedArgs )
{
AliasSubstitution subst( 0, namedArgs );
for( int ac = 0; ac < n_argn; ac++ )
subst.Substitute( &n_args[ac] );
}
else
{
for( int ac = w_argf+1; ac < w_argn; ac++ )
n_args[dst++].Set( w_args[ac] );
}
result->UpdateWorkingCommand( n_argn, n_args, e );
}
const StrPtr *
ClientCommand::GetInput()
{
return &inputVariable;
}
const StrPtr *
ClientCommand::GetOutput()
{
return &outputVariable;
}
void
ClientCommand::SetInput( const StrPtr *in )
{
inputVariable.Set( in );
}
void
ClientCommand::SetOutput( const StrPtr *out )
{
outputVariable.Set( out );
}
int
ClientCommand::Matches( CommandPattern *pattern, StrPtr *text )
{
// If the alias has named formal arguments, then the invocation must
// exactly match that with the same number of variable values, since
// we replace the variables by values using their position in the
// list. But if the alias has no named formal arguments, then the
// invocation can have any number of arguments, because we'll just
// glom them on at the end.
//
// Either way, the invocation's function name must exactly match
// the name of this alias.
if( !FunctionMatches( text ) )
return 0;
int n_args = pattern->Formals();
if( n_args > 0 && n_args != ( w_argn - w_argf - 1 ) )
return 0;
for( int ac = 0; ac < n_args; ac++ )
{
if( pattern->IsAVariable( ac ) )
continue;
StrRef var, val;
pattern->GetFormal( ac, &var );
GetActual( ac, &val );
if( var != val.Text() )
return 0;
}
return 1;
}
int
ClientCommand::FunctionMatches( StrPtr *text )
{
return ( w_argv != 0 && ( (*text) == (*w_argv) ) );
}
void
ClientCommand::GetActual( int ac, StrRef *val )
{
if( ac >= 0 && ac < ( w_argn - w_argf - 1 ) )
val->Set( w_args[ w_argf + ac + 1 ] );
}
void
ClientCommand::PrepareIO( StrDict *dict, Error *e )
{
ui = new ClientUserStrBuf( *w_opts );
ui->SetFormat( (*w_opts)[ 'F' ] );
if( inputVariable.Length() && dict )
{
ui->SetInputData( dict->GetVar( inputVariable ) );
StrPtr *xArg = (*w_opts)[ 'x' ];
if( xArg && xArg->Text()[0] == '-' )
xArg->Text()[0] = '<';
}
}
// Returns non-zero if this was a special operator, and was completed, or
// if there is an error. Otherwise, returns 0.
//
int
ClientCommand::HandleSpecialOperators(
int argc,
char **argv,
Options &opts,
Error *e )
{
if( argc && !strcmp( argv[0], "p4subst" ) )
{
OperatorP4Subst oper;
oper.Substitute( argc, argv, opts, ui, e );
return 1;
}
return 0;
}
/**************************************************************************
* AliasSubstitution: manages substituting variables in commands. *
**************************************************************************/
AliasSubstitution::AliasSubstitution(
Client *c,
StrDict *d )
{
this->client = c;
this->dict = d;
}
AliasSubstitution::~AliasSubstitution()
{
}
int
AliasSubstitution::Substitute( StrBuf *b )
{
// Variable references take the form $(var). When we find one of those
// in the buffer, we replace it with the variable's value. If we
// can't find a value, we leave the variable entirely alone, since
// it may get transformed subsequently by some other pass which knows
// the correct value for this variable.
//
// We return a code summarizing what we did:
// - 0: this buffer had no variable references, was unchanged
// - 1: this buffer had at least one var, successfully replaced
// - 2: this buffer had at least one var, at least one was unknown
// - 3: this buffer had a malformed reference, it seems.
int result = 0;
const char *p = b->Text();
const char *s = 0;
StrBuf value;
StrBuf b2;
StrBuf var;
while( p && *p )
{
if( s && ( *p == ')' ) )
{
// we've found the end of a variable reference.
var.Set( StrRef( s + 2, p - s - 2 ) );
GetValue( var, &value );
if( value.Length() )
{
b2.Clear();
b2 << StrRef( b->Text(), s - b->Text() );
b2 << value;
b2 << StrRef( p + 1 );
b->Set( b2 );
p = b->Text();
}
s = 0;
if( !result )
result = value.Length() > 0 ? 1 : 2;
else if( value.Length() == 0 )
result = 2;
}
else if( !s && ( *p == '$' && *(p+1) == '(' ) )
{
// we've found the start of a variable reference
s = p;
p += 2;
}
else
{
p++;
}
}
if( s )
result = 3;
return result;
}
void
AliasSubstitution::GetValue( StrPtr &var, StrBuf *val )
{
val->Clear();
if( var == "LT" )
{
val->Set( "<" );
return;
}
if( var == "GT" )
{
val->Set( ">" );
return;
}
if( var == "EQ" )
{
val->Set( "=" );
return;
}
if( client )
{
if( var == "P4USER" )
val->Set( client->GetUser() );
else if( var == "P4CLIENT" )
val->Set( client->GetClient() );
else if( var == "P4HOST" )
val->Set( client->GetHost() );
else if( var == "P4LANGUAGE" )
val->Set( client->GetLanguage() );
else if( var == "CWD" )
val->Set( client->GetCwd() );
else if( var == "OS" )
val->Set( client->GetOs() );
}
if( val->Length() > 0 )
return;
if( dict )
{
const StrPtr *s = dict->GetVar( var );
if( s )
val->Set( s );
}
}
/*************************************************************************
* ClientUserStrBuf: custom command line UI subclass of ClientUser which *
* is able to collect all the output of a command into a StrBuf object *
* and can then give that output to a subsequent command for its use. *
*************************************************************************/
ClientUserStrBuf::ClientUserStrBuf( Options &opts )
: ClientUserMunge( opts )
{
severity = E_EMPTY;
}
ClientUserStrBuf::~ClientUserStrBuf()
{
}
void
ClientUserStrBuf::Append( const char *s )
{
if( output.Length() > 0 )
output << "\n";
output << s;
}
int
ClientUserStrBuf::GetSeverity()
{
return severity;
}
int
ClientUserStrBuf::CommandFailed()
{
return GetSeverity() > E_INFO; // Should this be E_WARN?
}
void
ClientUserStrBuf::OutputError( const char *errBuf )
{
Append( errBuf );
}
void
ClientUserStrBuf::OutputInfo( char level, const char *data )
{
Append( data );
}
void
ClientUserStrBuf::OutputText( const char *data, int length )
{
Append( data );
}
void
ClientUserStrBuf::HandleError( Error *err )
{
Message( err );
}
void
ClientUserStrBuf::Message( Error *err )
{
// FIXME -- probably need to think about:
//
// if( err->IsInfo() )
// ...
// else
// ...
if( err->GetSeverity() > severity )
severity = err->GetSeverity();
if( format.Length() )
{
OutputStat( err->GetDict() );
}
else
{
StrBuf msg;
err->Fmt( msg, EF_PLAIN );
OutputInfo( 0, msg.Text() );
}
}
void
ClientUserStrBuf::OutputStat( StrDict *dict )
{
if( !format.Length() && dict )
{
if( dict->GetVar( "specdef" ) )
ClientUserMunge::OutputStat( dict );
else
ClientUser::OutputStat( dict );
return;
}
if( !dict )
return;
StrBuf out;
StrOps::Expand2( out, format, *dict );
Append( out.Text() );
}
const StrPtr *
ClientUserStrBuf::GetOutput()
{
return &output;
}
void
ClientUserStrBuf::SetFormat( const StrPtr *fmt )
{
if( fmt )
format.Set( fmt );
}
void
ClientUserStrBuf::SetInputData( const StrPtr *data )
{
if( data )
input.Set( data );
}
void
ClientUserStrBuf::InputData( StrBuf *b, Error *e )
{
if( input.Length() )
b->Set( input );
else
ClientUserMunge::InputData( b, e );
}
/***************************************************************************
* OperatorP4Subst: allows simple string substitutions in aliases *
***************************************************************************/
OperatorP4Subst::OperatorP4Subst()
{
splr = 0;
}
OperatorP4Subst::~OperatorP4Subst()
{
delete splr;
}
void
OperatorP4Subst::Substitute(
int argc,
char **argv,
Options &opts,
ClientUser *ui,
Error *e )
{
StrBuf line, result;
ui->InputData( &rawData, e );
if( e->Test() )
return;
splr = new StrPtrLineReader( &rawData );
RegMatch matcher( RegMatch::grep );
matcher.compile( argv[1], e );
if( e->Test() )
return;
int ln = 1, lnCount = splr->CountLines();
while( splr->GetLine( &line ) )
{
if( matcher.matches( line.Text(), e ) )
UpdateLine( &line, &result, &matcher, argv[2] );
else
result.Set( line );
if( ln++ < lnCount )
result << "\n";
ui->OutputInfo( 0, result.Text() );
}
}
void
OperatorP4Subst::UpdateLine(
StrBuf *line,
StrBuf *result,
RegMatch *matcher,
const char *replace )
{
result->Clear();
(*result) << StrRef( line->Text(), matcher->begin() );
(*result) << replace;
if( matcher->end() < line->Length() )
(*result) << line->Text() + matcher->end();
}