/* -*- 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; }