/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ #if !defined(_NET_FOSSIL_SCM_FOSSILAPP_H_INCLUDED_) #define _NET_FOSSIL_SCM_FOSSILAPP_H_INCLUDED_ /* ** 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/ ** ******************************************************************************* ** This file provides a basis for basic libfossil-using apps. It is intended ** to be used as the basis for test/demo apps, and not necessarily full-featured ** applications. */ /* Force assert() to always work... */ #if defined(NDEBUG) #undef NDEBUG #define DEBUG 1 #endif #include "fossil-scm/fossil.h" #include <assert.h> /* for the benefit of test apps */ #include <stdlib.h> /* EXIT_SUCCESS and friends */ /** @page page_fcli fcli (formerly FossilApp) ** ** ::fcli (formerly FossilApp) provides a small framework for ** bootstrapping simple libfossil applications. It is primarily ** intended for use with CLI apps implementing features similar to ** those in fossil(1). It provides the following basic services to ** applications: ** ** - The global ::fcli struct holds global state. ** ** - fcli_setup() bootstraps the environment. This must be the ** first call made into the API, as this replaces the libfossil ** memory allocator with a fail-fast variant to simplify app-level ** code a bit (by removing the need to check for OOM errors). This ** also registers an atexit(3) handler to clean up fcli-owned ** resources at app shutdown. ** ** - Automatically tries to open a checkout (and its associated ** repository) under the current directory, but not finding one ** is not an error (the app needs to check for this if it should ** be an error: use fsl_cx_db_repo() and fsl_cx_db_checkout()). ** ** - fcli_flag(), fcli_next_arg(), and friends ** provide uniform access to CLI arguments. ** ** - A basic help subsystem, triggered by the --help or -? CLI flags, ** or if the first non-flag argument is "help". Applications may ** optionally assign fcli.appHelp to a function which outputs ** app-specific help. ** ** - Basic error reporting mechanism. See fcli_err_report(). ** ** The source tree contains several examples of using fcli ** in the files named f-*.c. */ /** ** Ouputs the given printf-type expression (2nd arg) to ** fcli_printf() if fcli.verbose>=N, else it does nothing. */ #define VERBOSEN(N,pfexp) \ if(fcli.verbose>=(N)) do { \ fcli_printf("VERBOSE %d: ", (N)); \ fcli_printf pfexp; \ } while(0) /** Convenience form of VERBOSEN for level-2 verbosity. */ #define VERBOSE2(pfexp) VERBOSEN(2,pfexp) /** ** Convenience form of VERBOSEN for level-1 verbosity. Level 1 ** is intended for application-level use. */ #define VERBOSE(pfexp) VERBOSEN(1,pfexp) #if defined(__cplusplus) extern "C" { #endif /** ** The fcli_t type, accessed by clients via the ::fcli ** global instance, contains various data for managing a basic ** Fossil SCM application build using libfossil. ** ** Usage notes: ** ** - All non-const pointer members are owned and managed by the ** fcli API. Clients may reference them but must be aware of ** lifetimes if they plan to hold the reference for long. ** */ struct fcli_t { /** ** Holds a help function callback which should output any ** app-specific help. Should be set by client applications BEFORE ** calling fcli_setup() so that the ::fcli help subsystem can ** integrate the app. fcli.appName and fcli.userName will be set ** by the time this is called. */ void (*appHelp)(); /** ** The shared fsl_cx instance. It gets initialized by ** fcli_setup() and cleaned up post-main(). ** ** this->f is owned by this object and will be cleaned up at app ** shutdown (post-main). */ fsl_cx * f; /** ** fsl_malloc()'d repo db name string. */ char * repoDb; /** ** A verbosity level counter. Starts at 0 (no verbosity) and goes ** up for higher verbosity levels. Currently levels 1 and 2 are intended ** for app-level use and level 3 for library-level use. */ int verbose; /** ** If not NULL then fcli_setup() will attempt to open the ** checkout for the given dir, including its associated repo ** db. By default this is "." (the current directory). ** ** Applications can set this to NULL _before_ calling ** fcli_setup() in order to disable the automatic attemp to ** open a checkout under the current directory. Doing so is ** equivalent to using the --no-checkout|-C flags. */ char const * checkoutDir; /** ** The current list of CLI arguments. This list gets modified ** by fcli_flag() and friends. */ char ** argv; /** ** Current number of items in this->argv. */ int argc; /** ** Application's name. Currently argv[0] but needs to be ** adjusted for Windows platforms. */ char const * appName; /** ** The default user name, as provided by the --user|-U flags or ** (if one of those is not provided) fsl_guess_user_name(). */ char * userName; /** ** True if the --dry-run flag is seen during initial arguments ** processing. ::fcli does not use this itself - it is ** intended as a convenience for applications. Those which want ** to support a short-form flag can implement that like this: ** ** @code ** char dryRun = fcli.fDryRun ? fcli.fDryRun : ** fcli_flag("n", NULL); ** @endcode ** */ char fDryRun; /** ** Transient settings and flags. These are bits which are used ** during (or very shortly after) fcli_setup() but have no effect ** if modified after that. */ struct { /** ** Whether or not to enable fossil's SQL tracing. ** This should start with a negative value, which helps ** fcli_process_args() process it. Setting this after ** initialization has no effect. */ char traceSql; /** ** Set to true if fcli_setup() detects -? or --help in the ** argument list, or if the first non-flag argument is "help". */ char helpRequested; } transient; }; typedef struct fcli_t fcli_t; /** @var fcli ** ** This fcli_t instance is intended to act as a singleton. It ** holds all fcli-related global state. It gets initialized with ** default/empty state at app startup and gets fully initialized ** via fcli_setup(). ** ** Application startup with this API typically looks like: ** ** @code ** // from early on in main(): ** int rc; ** fcli.appHelp = fcli_local_help; ** rc = fcli_setup(argv, argc); ** if(FSL_RC_BREAK==rc) return 0; // --help|-?|help ** else if(rc) goto end; ** ** // Else proceed. At the end do: ** ** end: ** return (fcli_err_report(0)||rc) ? EXIT_FAILURE : EXIT_SUCCESS; ** **@endcode ** ** fcli.f holds the API's fsl_cx instance. It will be non-NULL (but ** might not have an opened checkout/repository) if fsl_setup() ** succeeds. */ fcli_t fcli; /** ** Should be called early on in main(), passed the arguments passed ** to main(). Returns 0 on success. Sets up the ::fcli instance ** and opens a checkout in the current dir by default. ** ** MUST BE CALLED BEFORE fsl_malloc() and friends are used, as this ** swaps out the allocator with one which aborts on OOM. ** ** If argument processing finds either of the (--help, -?) flags, ** or the first non-flag argument is "help", it sets ** fcli.transient.helpRequested to a true value, calls fcli_help(), ** and returns FSL_RC_BREAK, in which case the application should ** exit/return from main with code 0 immediately. ** fcli.transient.helpRequested is set to 1 if --help or -? are ** seen, 2 if "help" is the first non-flag argument, so clients can ** (if they care to) further distinguish between the two, e.g. by ** checking for a command after "help" in the latter case and ** showing command-specific help. ** ** Returns 0 on success. Errors other than FSL_RC_BREAK should be ** treated as fatal to the app, and fcli.f's error state ** _might_ contain info about the error. */ int fcli_setup(int argc, char * const * argv ); /** ** Calls fcli_local_help() then displays global help options ** to stdout. */ void fcli_help(); /** ** Works like printf() but sends its output to fsl_outputf() using ** the fcli.f fossil conext (if set) or fsl_fprintf() (to ** stdout). */ void fcli_printf(char const * fmt, ...); /** ** f_out() is a shorthand for fcli_printf(). */ #define f_out fcli_printf /** ** Searches fcli.argv for the given flag (pass it without leading ** dashes). If found, this function returns true, else it returns ** false. If value is not NULL then the flag, if found, is assumed to ** have a value, otherwise the flag is assumed to be a boolean. A flag ** with a value may take either one of these forms: ** ** -flag=value ** -flag value ** ** *value gets assigned to a COPY OF value part of the first form or a ** COPY OF the subsequent argument for the second form (copies are ** required in order to avoid trickier memory management here). On ** success it removes the flag (and its value, if any) from ** fcli.argv. Thus by removing all flags early on, the CLI ** arguments are left only with non-flag arguments to sift through. ** ** If it returns a string, the caller must eventually free it with ** fsl_free(). ** ** Flags may start with either one or two dashes - they are ** equivalent. */ char fcli_flag(char const * opt, char ** value); /** ** Works like fcli_flag() but tries two argument ** forms, in order. It is intended to be passed short and long ** forms, but can be passed two aliases or similar. ** ** @code ** char * v = NULL; ** fcli_flag2("n", "limit", &v); ** if(v) { ...; fsl_free(v); } ** @endcode */ char fcli_flag2(char const * opt1, char const * opt2, char ** value); /** ** Works similarly to fcli_flag2(), but if no flag is found and ** value is not NULL then *value is assigned to the return value of ** fcli_next_arg(1). In that case: ** ** - The return value will specify whether or not fcli_next_arg() ** returned a value or not. ** ** - If it returns true then *value is owned by the caller and it ** must eventually be freed using fsl_free(). ** ** - If it returns false, *value is not modified. ** ** The opt2 parameter may be NULL, but op1 may not. */ char fcli_flag_or_arg(char const * opt1, char const * opt2, char ** value); /** ** Clears any error state in fcli.f. */ void fcli_err_clear(); /** ** Sets fcli.f's error state, analog to fsl_cx_err_set(). ** Returns the code argument on success, some other non-0 value on ** a more serious error (e.g. FSL_RC_OOM which formatting the ** string). */ int fcli_err_set(int code, char const * fmt, ...); /** ** If ::fcli has any error state, it outputs its and returns the ** error code, else returns 0. If clear is true the error state is ** cleared. Returns 0 if ::fcli has not been initialized. The ** 2nd and 3rd arguments are assumed to be the __FILE__ and __LINE__ ** macro values of the call point. Use fcli_err_report() ** to get this. ** ** The format of the output depends partially on fcli.verbose. In ** verbose mode, the file/line info is included, otherwise it is ** elided. */ int fcli_err_report2(char clear, char const * file, int line); /** ** Convenience macro for using fcli_err_report2(). */ #define fcli_err_report(CLEAR) fcli_err_report2((CLEAR), __FILE__, __LINE__) /** ** Peeks at or takes the next argument from the CLI args. ** If take is true, it is removed from the args list and transfered ** to the caller (who must fsl_free() it), else ownership is not ** modified. */ char * fcli_next_arg(char take); /** ** If fcli.argv contains what looks like any flag arguments, ** this updates the fossil error state and returns true, else ** returns false. If outputError is true and an unused flag is ** found then the error state is immediately output (but not ** cleared). */ char fcli_has_unused_flags(char outputError); /** ** Typedef for general-purpose fcli call-by-name commands. */ typedef int (*FossilCommand_f)(); /** ** Describes a named callback command. ** ** @see fcli_dispatch_commands() */ struct FossilCommand { /** The name of the command. */ char const * name; /** The callback for this command. */ FossilCommand_f f; }; typedef struct FossilCommand FossilCommand; /** ** Expects an array of FossilCommands which contain a trailing ** sentry entry with a NULL name and callback. It searches the list ** for a command matching fcli_next_arg(). If found, it ** removes that argument from the list, calls the callback, and ** returns its result. If no command is found FSL_RC_NOT_FOUND is ** returned, the argument list is not modified, and the error state ** is updated with a description of the problem and a list of all ** command names in cmdList. ** ** If reportErrors is true then on error this function outputs ** the error result but it keeps the error state in place ** for the downstream use. */ int fcli_dispatch_commands( FossilCommand const * cmdList, char reportErrors); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* _NET_FOSSIL_SCM_FOSSILAPP_H_INCLUDED_ */