/* -*- 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.
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_ */