/* -*- 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 /* for the benefit of test apps */ #include /* 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. Reminder: this uses a while(0) look so that the macro can end with a semicolon without triggering a warning. */ #define FCLI_VN(N,pfexp) \ if(fcli.verbose>=(N)) do { \ fcli_printf("VERBOSE %d: ", (int)(N)); \ fcli_printf pfexp; \ } while(0) /** Convenience form of FCLI_VN for level-2 verbosity. */ #define FCLI_V2(pfexp) FCLI_VN(2,pfexp) /** Convenience form of FCLI_VN for level-1 verbosity. Levels 1 and 2 are intended for application-level use. */ #define FCLI_V(pfexp) FCLI_VN(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 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; /** 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; /** 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 { /** fsl_malloc()'d repo db name string made by args processing. */ char * repoDbArg; /** Set to true if fcli_setup() detects -? or --help in the argument list, or if the first non-flag argument is "help". */ char helpRequested; /** Don't use this - it will likely go away. The following text was at some point true but is currently a lie: If the --gmt flag is found, this is set to true. That causes the FSL_CX_F_LOCALTIME_GMT flag to be set on this->f. */ char gmtTime; } transient; /** Holds bits which can/should be configured by clients BEFORE calling fcli_setup(). */ 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; /** This output channel is used when initializing this->f. The default implementation uses fsl_outputer_FILE to output to stdout. */ fsl_outputer outputer; } config; }; 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. */ extern 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 ); /** Returns the libfossil context associated with the fcli API. */ fsl_cx * fcli_cx(); /** 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, ...) #if 0 /* Would be nice, but complains about our custom format options: */ __attribute__ ((__format__ (__printf__, 1, 2))) #endif ; /** f_out() is a shorthand for fcli_printf(). */ #define f_out fcli_printf /** Returns the verbosity level set via CLI args. 0 is no verbosity. */ int fcli_is_verbose(); /** 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_reset(); /** 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 when formatting the string). */ int fcli_err_set(int code, char const * fmt, ...); /** Returns the internally-used fsl_error instance which is used for propagating errors. The object is owned by ::fcli and MUST NOT be freed or otherwise abused by clients. It may, however, be passed to routines which take a fsl_error parameter to report errors (e.g. fsl_deck_output(). Returns NULL if fcli_setup() has not yet been called or after fcli has been cleaned up (post-main()). */ fsl_error * fcli_error(); /** If ::fcli has any error state, this outputs it and returns the error code, else returns 0. If clear is true the error state is cleared/reset, otherwise it is left in place. 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. See fcli_err_report() for a convenience form of this function. The format of the output depends partially on fcli_is_verbose(). In verbose mode, the file/line info is included, otherwise it is elided. @see fcli_err_report() */ 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. @see fcli_dispatch_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); /** A minor helper function intended to be passed the pending result code of the main() routine. This function outputs any pending error state in fcli. Returns one of EXIT_SUCCESS if mainRc is 0 and fcli had no pending error report, otherwise it returns EXIT_FAILURE. This function does not clean up fcli - that is handled via an atexit() handler. It is intended to be called once at the very end of main: @code int main(){ int rc; ...set up fcli...assign rc... return fcli_end_of_main(rc); } @endcode @see fcli_error() @see fcli_err_set() @see fcli_err_report() @see fcli_err_reset() */ int fcli_end_of_main(int mainRc); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* _NET_FOSSIL_SCM_FOSSILAPP_H_INCLUDED_ */