/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2013 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** 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.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
*/
#include "FossilApp.h"
#include "fossil-scm/fossil-internal.h"
const FossilAppT FossilApp_empty = FossilApp_empty_m;
FossilAppT FossilApp = FossilApp_empty_m;
void FossilApp_printf(char const * fmt, ...){
if(FossilApp.f){
va_list args;
va_start(args,fmt);
fsl_outputfv(FossilApp.f, fmt, args);
va_end(args);
}
}
/**
** Outputs a description of the global FossilApp CLI options to
** stdout.
*/
static void FossilApp_help_global_options();
static int FossilApp_open(){
int rc = 0;
fsl_cx * f = FossilApp.f;
assert(f);
if(FossilApp.repoDb){
VERBOSE(("Trying to open repo db file [%s]...\n", FossilApp.repoDb));
rc = fsl_repo_open_db( f, FossilApp.repoDb, 0 );
if(rc) return rc;
}
else if(FossilApp.checkoutDir){
fsl_buffer dir = fsl_buffer_empty;
char const * dirName;
rc = fsl_file_canonical_name(FossilApp.checkoutDir, &dir, 0);
assert(!rc);
dirName = (char const *)fsl_buffer_cstr(&dir);
VERBOSE(("Trying to open checkout from [%s]...\n",
dirName));
rc = fsl_checkout_open(f, dirName, fsl_buffer_size(&dir));
/* if(FSL_RC_NOT_FOUND==rc) rc = FSL_RC_NOT_A_CHECKOUT; */
if(rc){
if(!fsl_cx_err_get(f,NULL,NULL)){
rc = fsl_cx_err_set(f, rc, "Opening of checkout under "
"[%s] failed with code %d (%s).",
dirName, rc, fsl_rc_cstr(rc));
}
}
fsl_buffer_reserve(&dir, 0);
if(rc) return rc;
}
if(!rc && FossilApp.verbose){
fsl_db * dbC = fsl_cx_db_checkout(f);
fsl_db * dbR = fsl_cx_db_repo(f);
if(dbC){
VERBOSE(("Checkout DB name: %s\n",
fsl_buffer_cstr(&f->dbCkout.filename)));
}
if(dbR){
VERBOSE(("Opened repo db: %s\n", fsl_buffer_cstr(&f->dbRepo.filename)));
}
}
return rc;
}
void FossilApp_err_clear(){
if(FossilApp.f){
fsl_cx_err_clear(FossilApp.f);
}
}
static void FossilApp_shutdown(){
fsl_cx * f = FossilApp.f;
int rc = 0;
FossilApp.f = NULL;
VERBOSE(("Finalizing fsl_cx @%p\n", (void const *)f));
fsl_free(FossilApp.repoDb);
fsl_free(FossilApp.userName);
for( rc = 0; rc < FossilApp.argc; ++rc ){
fsl_free(FossilApp.argv[rc]);
}
fsl_free(FossilApp.argv);
FossilApp = FossilApp_empty;
if(f){
if(1 &&
fsl_cx_db_checkout(f)){
/* For testing/demo only: this is implicit
when we call fsl_cx_finalize().
*/
rc = fsl_checkout_close(f);
assert(0==rc);
VERBOSE(("Closed checkout/repo db(s).\n"));
}
fsl_cx_finalize( f );
}
}
void FossilApp_help(){
FossilApp_local_help();
FossilApp_help_global_options();
}
static int FossilApp_process_args( int argc, char * const * argv ){
int i;
fsl_buffer avb = fsl_buffer_empty;
int rc = 0;
char * cp;
FossilApp.appName = argv[0];
FossilApp.argc = 0;
rc = fsl_buffer_reserve(&avb, 10 * sizeof(char*));
if(rc) return rc;
FossilApp.argv = (char **)avb.mem;
for( i = 1; i < argc; ++i ){
char const * arg = argv[i];
if('-'==*arg){
char const * flag = arg+1;
while('-'==*flag) ++flag;
#define FLAG(F) if(0==strcmp(F,flag))
FLAG("help") {
FossilApp.helpRequested = 1;
continue;
}
FLAG("?") {
FossilApp.helpRequested = 1;
continue;
}
FLAG("V") {
FossilApp.verbose = 1;
continue;
}
FLAG("verbose") {
FossilApp.verbose = 1;
continue;
}
FLAG("C"){ /* Disable auto-open of checkout */
FossilApp.checkoutDir = NULL;
continue;
}
FLAG("S") {
FossilApp.traceSql = 1;
continue;
}
#undef FLAG
/* else fall through */
}
cp = fsl_strdup(arg);
if(!cp) return FSL_RC_OOM;
if(avb.capacity < (FossilApp.argc * sizeof(char*))){
rc = fsl_buffer_reserve(&avb, avb.capacity * 2);
if(rc){
fsl_free(cp);
return rc;
}
FossilApp.argv = (char **)avb.mem;
}
FossilApp.argv[FossilApp.argc++] = cp;
}
/** Now pull out our flags. We do this
in a second step so that we can use
the FossilApp_cli_flag() parsing.
*/
if( FossilApp_cli_flag("no-checkout", NULL) ){
FossilApp.checkoutDir = NULL;
}
if(FossilApp.traceSql<0){
FossilApp.traceSql = FossilApp_cli_flag("trace-sql", NULL);
}else{
/* Just make sure it's not left around in the args list. */
FossilApp_cli_flag("trace-sql", NULL);
}
FossilApp_cli_flag2("R","repo", &FossilApp.repoDb);
if(!FossilApp_cli_flag2("user", "U", &FossilApp.userName)){
FossilApp.userName = fsl_guess_user_name();
}
if(FossilApp.helpRequested){
FossilApp_help();
rc = FSL_RC_BREAK;
}
return rc;
}
char FossilApp_cli_flag(char const * opt, char ** value){
int i = 0;
int remove = 0;
char rc = 0;
fsl_size_t optLen = fsl_strlen(opt);
for( ; i < FossilApp.argc; ++i ){
char const * arg = FossilApp.argv[i];
char const * x;
char const * vp = NULL;
if('-' != *arg) continue;
rc = 0;
x = arg+1;
if('-' == *x) { ++x;}
if(0 != fsl_strncmp(x, opt, optLen)) continue;
if(!value){
if(x[optLen]) continue /* not exact match */;
/* Treat this as a boolean. */
rc = 1;
++remove;
break;
}else{
/* -FLAG VALUE or -FLAG=VALUE */
if(x[optLen] == '='){
rc = 1;
vp = x+optLen+1;
++remove;
}
else if(x[optLen]) continue /* not an exact match */;
else if(i<(FossilApp.argc-1)){
rc = 1;
vp = FossilApp.argv[i+1];
++remove;
}
else{
rc = 0;
--remove /* leave --opt in the args */;
}
if(rc) *value = fsl_strdup(vp);
break;
}
}
if(remove>0){
int x;
for( x = 0; x < remove; ++x ){
fsl_free(FossilApp.argv[i+x]);
FossilApp.argv[i+x] = NULL;
}
for( ; i < FossilApp.argc; ++i ){
FossilApp.argv[i] = FossilApp.argv[i+remove];
}
FossilApp.argc -= remove;
FossilApp.argv[i] = NULL;
}
return rc;
}
char FossilApp_cli_flag2(char const * shortOpt,
char const * longOpt,
char ** value){
char const rc = FossilApp_cli_flag(shortOpt, value);
return (rc || !longOpt) ? rc : FossilApp_cli_flag(longOpt, value);
}
/**
** Should be called early on in main(), passed the arguments passed
** to main(). Returns 0 on success. Sets up the FossilApp instance
** and opens a checkout in the current dir by default. It returns
** FSL_RC_BREAK if argument processing finds either of the (--help, -?)
** flags, and sets FossilApp.helpRequested to a true value.
*/
int FossilApp_setup(int argc, char * const * argv ){
fsl_cx * f = NULL;
int rc = 0;
fsl_init_param init = fsl_init_param_empty;
atexit(FossilApp_shutdown);
rc = FossilApp_process_args(argc, argv);
if(rc) return rc;
init.output = fsl_outputer_FILE;
init.output.state.state = stdout;
init.config.traceSql = FossilApp.traceSql;
rc = fsl_cx_init( &f, &init );
FossilApp.f = f;
VERBOSE(("Initialized fsl_cx @%p\n", (void const *)f));
if(!rc && (FossilApp.checkoutDir || FossilApp.repoDb)){
rc = FossilApp_open();
if(!FossilApp.repoDb
&& FossilApp.checkoutDir
&& (FSL_RC_NOT_FOUND == rc)) rc = 0;
}
return rc;
}
void FossilApp_help_global_options(){
puts("\nGlobal FossilApp options and features:\n");
puts("\t--help|-? triggers the app-specific help.\n");
puts("\t--no-checkout|-C supresses the automatic attempt "
"to open a checkout under the current directory. "
"This is implied by -R, to avoid opening a "
"mismatched checkout/repo combination.\n");
puts("\t--repo|-R=DBFILE specifies a repository "
"db to open.\n");
puts("\t--trace-sql|-S enables SQL tracing.\n");
printf("\t--user|-U=name [default=%s] sets the default Fossil "
"user for operations which need one.\n\n", FossilApp.userName);
puts("\t--verbose|-V enables verbose mode (extra output).\n");
puts("\nAll flags may be preceeded by - or -- and "
"flags taking values may be in the form -FLAG=VALUE "
"or -FLAG VALUE. Flags not taking values are "
"treated as booleans.\n");
}
int FossilApp_err_report2(char clear, char const * file, int line){
int errRc = 0;
if(FossilApp.f){
char const * msg = NULL;
errRc = fsl_cx_err_get( FossilApp.f, &msg, NULL );
if(errRc || msg){
fprintf(stderr, "%s %s:%d: Fossil error: code #%d (%s): %s\n",
FossilApp.appName,
file, line, errRc, fsl_rc_cstr(errRc), msg);
}
if(clear) FossilApp_err_clear();
}
return errRc;
}
char * FossilApp_next_arg(char take){
char * rc = (FossilApp.argc>0) ? FossilApp.argv[0] : NULL;
if(rc && take){
int i;
--FossilApp.argc;
for(i = 0; i < FossilApp.argc; ++i){
FossilApp.argv[i] = FossilApp.argv[i+1];
}
FossilApp.argv[FossilApp.argc] = NULL;
}
return rc;
}
char FossilApp_has_unused_flags(char outputError){
int i;
for( i = 0; i < FossilApp.argc; ++i ){
char const * arg = FossilApp.argv[i];
if('-'==*arg){
fsl_cx_err_set(FossilApp.f, FSL_RC_MISUSE,
"Unhandled/unknown flag: %s",
arg);
if(outputError){
FossilApp_err_report(0);
}
return 1;
}
}
return 0;
}