/* ====================================================================
* Copyright (c) 2003-2006, Martin Hauner
* http://subcommander.tigris.org
*
* Subcommander is licensed as described in the file doc/COPYING, which
* you should have received as part of this distribution.
* ====================================================================
*/
// sys
#include <algorithm>
#include <assert.h>
// apr
#include <apr_pools.h>
#include <apr_getopt.h>
#include <apr_strings.h>
// svn
#include <svn_diff.h>
// sc
#include "config.h"
#include "MainWindow.h"
#include "Diff3Widget.h"
#include "DiffParam.h"
#include "ConfigManager.h"
#include "sublib/MacStyle.h"
#include "sublib/Utility.h"
#include "sublib/MsgHandler.h"
#include "sublib/ErrorDialog.h"
#include "util/apr.h"
#include "util/Exception.h"
// qt
#include <qapplication.h>
/////////////////////////////////////////////////////////////////////
typedef struct sc_options_state
{
sc_options_state()
{
help = false;
debug = false;
whitespace = true;
encoding = "*"; // means => APR_LOCALE_CHARSET;
labels[0] = 0;
labels[1] = 0;
labels[2] = 0;
}
bool help;
bool debug;
bool whitespace; // diff whitespace
sc::String encoding;
const char* labels[3];
} sc_options_state_t;
typedef int (*cmd_func)( sc_options_state_t *optState, apr_getopt_t* opt );
const int MaxAliases = 3;
const int MaxOptions = 10;
typedef struct sc_command
{
const char* name;
cmd_func func;
const char* aliases[MaxAliases];
int options[MaxOptions];
const char* desc;
const char* usage;
} sc_command_t;
/////////////////////////////////////////////////////////////////////
const sc_command_t* lookupCommand( const sc_command_t table[], const char* command )
{
assert(table);
if( ! command )
{
return 0;
}
for( int i = 0; table[i].name; i++ )
{
if( strcasecmp(command, table[i].name) == 0 ) // stricmp
{
return &table[i];
}
for( int a = 0; a < MaxAliases; a++ )
{
if( ! table[i].aliases[a] )
{
break;
}
if( strcasecmp( command, table[i].aliases[a] ) == 0 ) //stricmp
{
return &table[i];
}
}
}
return 0;
}
/////////////////////////////////////////////////////////////////////
int nop( sc_options_state_t *optState, apr_getopt_t* os );
int help( sc_options_state_t *optState, apr_getopt_t* os );
int diff( sc_options_state_t *optState, apr_getopt_t* os );
int diff3( sc_options_state_t *optState, apr_getopt_t* os );
////////////////////////////////////////////////////////////////////
const int WhitespaceOff = 300;
const int WhitespaceOn = 301;
const apr_getopt_option_t sc_options[] =
{
{ "help", 'h', 0, "show help on a subcommand" },
{ NULL, '?', 0, "show help on a subcommand" },
{ "w-", WhitespaceOff, 0, "whitespaces off (ignore)" },
{ "w+", WhitespaceOn, 0, "whitespaces on" },
{ "encoding", 'e', 1, "encoding/codepage (iconv)" },
{ "label", 'L', 1, "label for a given file (per file)" },
{ "debug", 'd', 0, "print debug info to stderr" },
{ 0, 0, 0, 0 }
};
const sc_command_t sc_commands[] =
{
{
"diff", diff, {"diff2", 0}, { 'e', 'L', WhitespaceOn, WhitespaceOff, '\0' },
"display differences of two files.\n",
"usage: diff original modified\n"
},
{
"diff3", diff3, {"merge", 0}, { 'e', 'L', WhitespaceOn, WhitespaceOff,'\0' },
"visually merge differences of modifed and latest.\n",
"usage: diff3 original modified latest merged\n"
},
{
"help", help, {"?", "h", 0}, {0},
"display this usage message.\n",
"usage: help [SUBCOMMAND...]\n"
},
{ 0
}
};
sc_command_t nullCommand =
{
"null", nop, {0}, {0},
"do nothing.\n"
};
/////////////////////////////////////////////////////////////////////
const char helpHeader[] =
"usage: submerge <subcommand> [options] [args]\n"
"Type 'submerge help <subcommand>' for help on a specific subcommand.\n"
"\n"
"Available subcommands:\n";
const char helpFooter[] =
"\n"
"Subcommander is a Subversion gui client, diff and merge tool.\n"
"For additional information, see http://subcommander.tigris.org/\n"
"\n"
"Subversion is a tool for revision control.\n"
"For additional information, see http://subversion.tigris.org/\n"
"\n";
/////////////////////////////////////////////////////////////////////
// cmd_funcs
const int Failure = -1;
const int Success = 0;
const int Close = 1;
int nop( sc_options_state_t *optState, apr_getopt_t* os )
{
return Success;
}
void printfAliases( const sc_command_t* cmd )
{
bool firstOpt = true;
for( int cntAl = 0; cntAl < MaxAliases; cntAl++ )
{
if( ! cmd->aliases[cntAl] )
{
break;
}
if( firstOpt )
{
printf( " (" );
firstOpt = false;
}
else
{
printf( ", " );
}
printf( "%s", cmd->aliases[cntAl] );
}
if( !firstOpt )
{
printf( ")" );
}
}
const apr_getopt_option_t* getOption( int option )
{
int i = 0;
while( sc_options[i].optch != 0 )
{
if( sc_options[i].optch == option )
{
return &sc_options[i];
}
i++;
}
return NULL;
}
void printfOptions( const sc_command_t* cmd )
{
if( cmd->options[0] )
{
printf( "Valid options:\n" );
}
for( int cnt = 0; cnt < MaxOptions; cnt++ )
{
if( ! cmd->options[cnt] )
{
break;
}
sc::String text;
char tmp[200];
const apr_getopt_option_t* opt = getOption(cmd->options[cnt]);
bool optch = false;
if( opt->optch < 255 )
{
sprintf( tmp, "-%c ", opt->optch );
text += tmp;
optch = true;
}
if( opt->name && optch )
{
sprintf( tmp, "[--%s] ", opt->name );
text += tmp;
}
else if( opt->name )
{
sprintf( tmp, "--%s ", opt->name );
text += tmp;
}
if( opt->has_arg )
{
text += "arg";
}
printf( " %-25s : %s\n", (const char*)text,
opt->description ? opt->description : "missing description!"
);
}
}
int help( sc_options_state_t *optState, apr_getopt_t* os )
{
const char* firstArg = 0;
if( ! optState || ! os )
{
goto short_help;
}
firstArg = os->argv[os->ind++];
if( firstArg )
{
const sc_command_t* command = lookupCommand( sc_commands, firstArg );
if( !command )
{
fprintf( stderr, "unknown command: %s\n", firstArg );
goto short_help;
}
printf( "%s: %s", command->name, command->desc );
printf( command->usage );
printf( "\n\n" );
printfOptions( command );
}
else
{
printf( helpHeader );
for( int cntCmd = 0; sc_commands[cntCmd].name; cntCmd++ )
{
printf( " %s", sc_commands[cntCmd].name );
printfAliases( &sc_commands[cntCmd] );
printf( "\n" );
}
printf( helpFooter );
}
return Success;
short_help:
fprintf( stderr, "type 'sm help' for usage.\n" );
return Failure;
}
int help()
{
return help( NULL, NULL );
}
int diff( sc_options_state_t *optState, apr_getopt_t* os )
{
if( os->argc - os->ind != 2 )
{
// todo print usage
return Failure;
}
MainWindow* mw = (MainWindow*)qApp->mainWidget();
DiffParamPtr param( new DiffParam() );
param->_type = DiffParam::Diff;
param->_original = FileDataPtr( new FileData( sc::String(os->argv[os->ind]), sc::String(optState->encoding) ) );
param->_modified = FileDataPtr( new FileData( sc::String(os->argv[os->ind+1]), sc::String(optState->encoding) ) );
param->_encoding = optState->encoding;
param->_whitespace = optState->whitespace;
param->_originalLabel = optState->labels[0];
param->_modifiedLabel = optState->labels[1];
const sc::Error* err = mw->diff( param );
if( err )
{
return Close;
}
return Success;
}
int diff3( sc_options_state_t *optState, apr_getopt_t* os )
{
if( os->argc - os->ind != 4 )
{
return Failure;
}
MainWindow* mw = (MainWindow*)qApp->mainWidget();
DiffParamPtr param( new DiffParam() );
param->_type = DiffParam::Diff3;
param->_original = FileDataPtr( new FileData( sc::String(os->argv[os->ind]), sc::String(optState->encoding) ) );
param->_modified = FileDataPtr( new FileData( sc::String(os->argv[os->ind+1]), sc::String(optState->encoding) ) );
param->_latest = FileDataPtr( new FileData( sc::String(os->argv[os->ind+2]), sc::String(optState->encoding) ) );
param->_merged = os->argv[os->ind+3];
param->_encoding = optState->encoding;
param->_whitespace = optState->whitespace;
param->_originalLabel = optState->labels[0];
param->_modifiedLabel = optState->labels[1];
param->_latestLabel = optState->labels[2];
const sc::Error* err = mw->diff3( param );
if( err )
{
return Close;
}
return Success;
}
///////////////////////////////////////////////////////////////////////////////
// entry point
static apr_pool_t* pool;
void cleanup()
{
apr::destroyPool(pool);
}
int main( int argc, char* argv[] )
{
QApplication* app = 0;
try
{
setLocale();
// init stacktrace
initStackProcess();
// init apr
apr::initialize(argc,argv);
// init qt
app = new QApplication( argc, argv );
setAppName("submerge");
// redirect qDebug etc.
installMessageHandler();
#if !defined(Q_WS_X11)
QApplication::setStyle( new MacStyle() );
#endif // Q_WS_X11
// read config
ConfigManager config;
config.load();
// set font
// if we set the font allthough it is the same font as the default (system) font
// we get a different font in popup menus. Looks like a bug in Qt 3.
if( qApp->font() != config.getGeneralFont() )
{
QApplication::setFont( config.getGeneralFont() );
}
// adjust locale path
setLocalePath( config.getL10n() ? app->applicationDirPath() : "/no_l10n" );
// setup gui
MainWindow* mw = new MainWindow(&config);
app->setMainWidget(mw);
// create pool for parsing command line
pool = apr::createPool();
atexit( cleanup );
// parse options...
apr_getopt_t* os = 0;
sc_options_state_t optState;
apr_status_t status;
optState.whitespace = config.getOptWhitespace();
status = apr_getopt_init( &os, pool, argc, argv );
os->interleave = 1;
while( true )
{
int option_ch;
const char* option_arg;
status = apr_getopt_long( os, sc_options, &option_ch, &option_arg );
if( APR_STATUS_IS_EOF(status) )
{
break;
}
else if( status != APR_SUCCESS )
{
help();
return EXIT_FAILURE;
}
char* dup = apr_pstrdup( pool, option_arg );
switch( option_ch )
{
case 'e':
{
optState.encoding = dup;
break;
}
case 'd':
{
optState.debug = true;
break;
}
case WhitespaceOff:
{
optState.whitespace = false;
break;
}
case WhitespaceOn:
{
optState.whitespace = true;
break;
}
case 'L':
{
static int cnt = 0;
if( cnt < 3 )
{
optState.labels[cnt] = dup;
cnt++;
}
break;
}
case 'h':
case '?':
{
optState.help = true;
break;
}
}
}
if( optState.debug )
{
fprintf( stderr, "submerge dbg: arguments,\n" );
for( int i=0; i < argc; i++ )
{
fprintf( stderr, "%02d: %s\n", i, argv[i] );
}
}
// lookup the command
const sc_command_t* command = NULL;
if( optState.help )
{
command = lookupCommand( sc_commands, "help" );
}
if( !command )
{
const char* firstArg = os->argv[os->ind++];
command = lookupCommand( sc_commands, firstArg );
if( !command )
{
command = &nullCommand;
//if( appType == atConsole )
//{
//help();
//return EXIT_FAILURE;
//}
}
}
if( optState.debug )
{
fprintf( stderr, "submerge dbg: running cmd %s\n", command->name );
}
// at last, call the command....
int res = command->func( &optState, os );
// if it was a help command, just return.
if( optState.help || strcmp(command->name, "help")==0 )
{
return res;
}
// run the gui
if( res == Success )
{
mw->show();
int result = app->exec();
config.save();
delete app;
return result;
}
else if( res == Close )
{
delete app;
return EXIT_SUCCESS;
}
else
{
fprintf( stderr, "submerge: failed to run %s command.\n", command->name );
return EXIT_FAILURE;
}
}
catch( sc::Exception& e )
{
ErrorDialog* dlg = new ErrorDialog(e);
dlg->exec();
return EXIT_FAILURE;
}
}
syntax highlighted by Code2HTML, v. 0.9.1