grim/gplate

Properly generate the documentation (at least: 100% symbol coverage).

I hate to flatten history like this, as this represented by 37 commits
in my local repository. However, I could _not_ get the result to
merge for some reason that I didn't have the time to figure out, so I
flattened it down to a single patch against upstream, and fixed the
resulting diff, and this is that commit.

In a nutshell, this commit:

- gets rid of the SGML template files, as the content that they had
was moved to source code files so that the documentation is closer
to the code, and also because the SGML templates were blocking some
already existing doc-comments in the source code.

- Gets rid of the gplate-sections.txt file, as gtk-doc builds it for
us.

- Gets rid of the gplate.types file. It is generated from scanning
the source code, and adjusted by sed to eliminate things that
gtk-doc won't be able to runtime introspect since they're not
currently "there".

- Hooks the doc/gplates directory into the build system (just
barely). The real work is not even done by CMake, as I couldn't
figure out how to get it to do what I wanted, which leads to...

- The documentation is _actually_ built by doc/gplate/build-docs.sh,
which contains hardcoded values that I could not figure out how to
get CMake to expose certain things. This means that the build
system doesn't actually know jack about the documentation (and this
also means that the documentation won't be installed as part of the
"make install" target, either). (BUT, the documentation is
available and BUILDS!)

While this commit makes things better than they were before the
commit, I would not call this commit overly polished. I fully expect
the shell script to be replaced by something done in/with/via CMake
directly, I just don't know how to do it that way.
/*
* GPlate - GObject based templating library
* Copyright (C) 2007-2012 Gary Kramlich <grim@reaperworld.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library 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. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <gplate/gplate-library.h>
#include <gplate/gplate-errors.h>
/******************************************************************************
* Structs
*****************************************************************************/
typedef struct {
GType function;
GType tag;
} GPlateFunctionData;
typedef struct {
GType tag;
gchar *prefix;
gchar *suffix;
GType default_function;
} GPlateTagData;
/******************************************************************************
* Globals
*****************************************************************************/
static GHashTable *functions = NULL;
static GHashTable *tags = NULL;
static GType default_tag = G_TYPE_INVALID;
/******************************************************************************
* GPlateFunctionData API
*****************************************************************************/
static GPlateFunctionData *
gplate_function_data_new(GType function, GType tag) {
GPlateFunctionData *data = g_new(GPlateFunctionData, 1);
data->function = function;
data->tag = tag;
return data;
}
static void
gplate_function_data_free(GPlateFunctionData *data) {
g_free(data);
}
/******************************************************************************
* GPlateTagData API
*****************************************************************************/
static GPlateTagData *
gplate_tag_data_new(GType tag, GError **error) {
GPlateTagData *data = NULL;
GPlateTagClass *klass = NULL;
const gchar *prefix = NULL, *suffix = NULL;
klass = g_type_class_ref(tag);
prefix = gplate_tag_class_get_prefix(klass);
suffix = gplate_tag_class_get_suffix(klass);
if(!prefix && suffix) {
if(error) {
*error =
g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_TAG_NO_PREFIX_HAVE_SUFFIX,
"tag '%s' does not have a prefix, but has a "
"suffix.",
g_type_name(tag));
}
return NULL;
} else if(prefix && !suffix) {
if(error) {
*error =
g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_TAG_HAVE_PREFIX_NO_SUFFIX,
"tag '%s' has a prefix, but does not have a "
"suffix.",
g_type_name(tag));
}
return NULL;
}
if(!prefix && !suffix) {
if(default_tag != G_TYPE_INVALID) {
if(error) {
*error =
g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_DEFAULT_TAG_REGISTERED,
"A default tag named '%s' has already been "
"registered.",
g_type_name(default_tag));
}
return NULL;
} else {
default_tag = tag;
}
}
data = g_new(GPlateTagData, 1);
data->tag = tag;
data->prefix = (prefix) ? g_strdup(prefix) : NULL;
data->suffix = (suffix) ? g_strdup(suffix) : NULL;
data->default_function = G_TYPE_INVALID;
g_type_class_unref(klass);
return data;
}
static void
gplate_tag_data_free(GPlateTagData *data) {
g_free(data->prefix);
g_free(data->suffix);
g_free(data);
}
/******************************************************************************
* Inlines
*****************************************************************************/
static inline void
gplate_library_lazy_init(void) {
if(G_UNLIKELY(functions == NULL)) {
functions =
g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
(GDestroyNotify)gplate_function_data_free);
}
if(G_UNLIKELY(tags == NULL)) {
tags = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
(GDestroyNotify)gplate_tag_data_free);
}
}
static inline gboolean
gplate_library_check_type(GType type, GType parent, GError **error) {
if(!g_type_is_a(type, parent)) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_TYPE_IS_NOT_DESCENDANT,
"%s is not a subclass of %s.",
g_type_name(type),
g_type_name(parent));
}
return FALSE;
}
if(G_TYPE_IS_ABSTRACT(type)) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_TYPE_IS_ABSTRACT,
"%s is abstract.",
g_type_name(type));
}
return FALSE;
}
return TRUE;
}
/******************************************************************************
* GPlateLibrary API
*****************************************************************************/
/**
* gplate_library_add_function:
* @name: The name of the function.
* @function: The #GType of the function.
* @error: The return address for errors.
*
* Adds @function to the library.
*
* Return Value: TRUE on success, otherwise FALSE with @error set.
*/
gboolean
gplate_library_add_function(const gchar *name, GType function, GError **error)
{
GPlateFunctionData *data = NULL;
g_return_val_if_fail(name, FALSE);
gplate_library_lazy_init();
if(!gplate_library_check_type(function, GPLATE_TYPE_FUNCTION, error))
return FALSE;
data = g_hash_table_lookup(functions, name);
if(data) {
/* if the function is already registered with the same name and
* function, return TRUE and jump out.
*/
if(data->function == function)
return TRUE;
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_FUNCTION_NAME_EXISTS,
"Function name '%s' is already registered "
"to '%s'",
name, g_type_name(data->function));
}
return FALSE;
}
data = gplate_function_data_new(function, G_TYPE_INVALID);
g_hash_table_insert(functions, g_strdup(name), data);
return TRUE;
}
/**
* gplate_library_remove_function:
* @name: The name of the function to remove.
* @error: Return address for errors.
*
* Removes a function from the library by it's name.
*
* Return Value: TRUE if the function was removed, otherwise FALSE.
*/
gboolean
gplate_library_remove_function(const gchar *name, GError **error) {
gboolean ret;
g_return_val_if_fail(name, FALSE);
ret = g_hash_table_remove(functions, name);
if(!ret && error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_FUNCTION_NAME_NOT_FOUND,
"Function '%s' was not found.",
name);
}
return ret;
}
/**
* gplate_library_add_tag:
* @tag: The #GPlateTag's GType to add.
* @error: The return address for any errors.
*
* Adds a tag to the library.
*
* Tags must be added to the library via this function for a template to
* handle them.
*
* Return Value: TRUE on success, FALSE on failure with @error set.
*/
gboolean
gplate_library_add_tag(GType tag, GError **error) {
GPlateTagData *data = NULL;
gplate_library_lazy_init();
if(!gplate_library_check_type(tag, GPLATE_TYPE_TAG, error))
return FALSE;
data = g_hash_table_lookup(tags, GSIZE_TO_POINTER(tag));
if(data) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_TAG_EXISTS,
"Tag '%s' is already in the library",
g_type_name(tag));
}
return FALSE;
}
data = gplate_tag_data_new(tag, error);
if(!data)
return FALSE;
g_hash_table_insert(tags, GSIZE_TO_POINTER(data->tag), data);
return TRUE;
}
/**
* gplate_library_remove_tag:
* @tag: The #GType of the tag to remove from the library.
* @error: The return address for any errors.
*
* Unregisters @tag from the library.
*
* Return Value: TRUE on success, FALSE on failure with @error set.
*/
gboolean
gplate_library_remove_tag(GType tag, GError **error) {
gpointer data;
gplate_library_lazy_init();
if(!gplate_library_check_type(tag, GPLATE_TYPE_TAG, error))
return FALSE;
data = g_hash_table_lookup(tags, GSIZE_TO_POINTER(tag));
if(!data) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_TAG_NOT_FOUND,
"Tag '%s' is not in the library.",
g_type_name(tag));
}
return FALSE;
}
g_hash_table_remove(tags, GSIZE_TO_POINTER(tag));
return TRUE;
}
/**
* gplate_library_bind_function:
* @name: The name of the function.
* @tag: The #GType of the tag.
* @error: The return address for errors.
*
* Binds the already registered function @name to @tag.
*
* Return Value: TRUE on success, FALSE on failure with @error set.
*/
gboolean
gplate_library_bind_function(const gchar *name, GType tag, GError **error) {
GPlateFunctionData *data = NULL;
gplate_library_lazy_init();
g_return_val_if_fail(name, FALSE);
if(!gplate_library_check_type(tag, GPLATE_TYPE_TAG, error))
return FALSE;
data = g_hash_table_lookup(functions, name);
if(!data) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_FUNCTION_NAME_NOT_FOUND,
"There is no function registered with the "
"name '%s'",
name);
}
return FALSE;
}
data->tag = tag;
return TRUE;
}
/**
* gplate_library_unbind_function:
* @name: The name of the function to unbind.
* @error: The return address for any errors.
*
* Unbinds @name.
*
* If @name is not already bound, this will return FALSE with a
* GPLATE_ERROR_LIBRARY_FUNCTION_NOT_BOUND error.
*
* Return Value: TRUE on success, FALSE on failure with @error set.
*/
gboolean
gplate_library_unbind_function(const gchar *name, GError **error) {
GPlateFunctionData *data = NULL;
g_return_val_if_fail(name, FALSE);
gplate_library_lazy_init();
data = g_hash_table_lookup(functions, name);
if(!data) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_FUNCTION_NAME_NOT_FOUND,
"No function named '%s' is registered",
name);
}
return FALSE;
}
if(data->tag == G_TYPE_INVALID) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_FUNCTION_NOT_BOUND,
"Function '%s' is not bound to a tag",
name);
}
return FALSE;
}
data->tag = G_TYPE_INVALID;
return TRUE;
}
/**
* gplate_library_add_bound_function:
* @name: The name of the function.
* @function: The #GType of the function.
* @tag: The #GType of the tag.
* @error: The return address for any errors.
*
* Registers @function with @name and then binds it to @tag.
*
* This is just a wrapper around #gplate_library_add_function and
* #gplate_library_bind_function.
*
* Return Value: TRUE on success, otherwise FALSE with @error set.
*/
gboolean
gplate_library_add_bound_function(const gchar *name, GType function, GType tag,
GError **error)
{
gboolean ret;
g_return_val_if_fail(name, FALSE);
gplate_library_lazy_init();
ret = gplate_library_add_function(name, function, error);
if(!ret)
return FALSE;
ret = gplate_library_bind_function(name, tag, error);
return ret;
}
/**
* gplate_library_lookup_function:
* @name: The name of the function to find.
* @function: Return address for the #GType of the functions.
* @tag: Return address for the #GType of the associated tag.
* @error: Return address for any errors.
*
* Looks for a function in the library.
*
* If the function is found, @function and @type will be set appropriately.
*
* If the function is not found, @function and @type are undefined, and error
* will be set.
*
* Return Value: TRUE if @name is found in the library, otherwise FALSE.
*/
gboolean
gplate_library_lookup_function(const gchar *name, GType *function, GType *tag,
GError **error)
{
GPlateFunctionData *data = NULL;
g_return_val_if_fail(name, FALSE);
data = g_hash_table_lookup(functions, name);
if(!data) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_FUNCTION_NAME_NOT_FOUND,
"Function '%s' was not found.",
name);
}
return FALSE;
}
if(function)
*function = data->function;
if(tag)
*tag = data->tag;
return TRUE;
}
/**
* gplate_library_lookup_function_for_tag:
* @name: The name of the function to lookup.
* @tag: The #GType of the tag.
* @error: The return address for any error.
*
* Looks up a function named @name that is bound to @tag.
*
* Return Value: The #GType of the function on success, otherwise
* #G_TYPE_INVALID on failure with @error set.
*/
GType
gplate_library_lookup_function_for_tag(const gchar *name, GType tag,
GError **error)
{
GType type = G_TYPE_INVALID, function = G_TYPE_INVALID;
gplate_library_lazy_init();
if(!gplate_library_check_type(tag, GPLATE_TYPE_TAG, error))
return G_TYPE_INVALID;
if(!gplate_library_lookup_function(name, &function, &type, error))
return G_TYPE_INVALID;
if(type != tag) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_FUNCTION_NOT_BOUND_TO_TAG,
"Function '%s' is not bound to '%s'",
name,
g_type_name(tag));
}
return G_TYPE_INVALID;
}
return function;
}
/**
* gplate_library_lookup_tag:
* @tag: The GType of the tag to lookup.
* @prefix: The return address for the prefix.
* @suffix: The return address for the suffix.
* @error: The return address for any errors.
*
* Looks for @tag in the library. If the tag is found, TRUE is returned and
* @prefix and @suffix will be set. NULL can be passed for @prefix and
* @suffix. If @prefix and @suffix are non-NULL they must be #g_free'd.
*
* If @tag is not found in the library, FALSE will be returned and @error will
* be set.
*
* Return Value: TRUE on success, FALSE otherwise.
*/
gboolean
gplate_library_lookup_tag(GType tag, gchar **prefix, gchar **suffix,
GError **error)
{
GPlateTagData *data = NULL;
gplate_library_lazy_init();
data = g_hash_table_lookup(tags, GSIZE_TO_POINTER(tag));
if(!data) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_TAG_NOT_FOUND,
"Tag '%s' was not found in the library",
g_type_name(tag));
}
return FALSE;
}
if(prefix)
*prefix = (data->prefix) ? g_strdup(data->prefix) : NULL;
if(suffix)
*suffix = (data->suffix) ? g_strdup(data->suffix) : NULL;
return TRUE;
}
/**
* gplate_library_get_default_tag:
*
* Gets the default tag if one has been registered.
*
* A default tag is one that does not have a prefix or a suffix. It is used
* when no other tags match.
*
* Return Value: The default tag if one has been registered, otherwise
* #G_TYPE_INVALID.
*/
GType
gplate_library_get_default_tag(void) {
return default_tag;
}
/**
* gplate_library_set_default_function_for_tag:
* @name: The name of the function.
* @tag: The #GType of the tag.
* @error: The return address for any errors.
*
* Sets the default function of @tag to @name.
*
* A default function is used when the contents of a tag do not match any
* functions that are registered to the tag.
*
* For example, the default function of #GPlateCommentTag is
* #GPlateNoopFunction.
*
* Return Value: TRUE on success, otherwise FALSE with @error set.
*/
gboolean
gplate_library_set_default_function_for_tag(const gchar *name, GType tag,
GError **error)
{
GPlateTagData *tdata = NULL;
GType function = G_TYPE_INVALID;
g_return_val_if_fail(name, FALSE);
gplate_library_lazy_init();
tdata = g_hash_table_lookup(tags, GSIZE_TO_POINTER(tag));
if(!tdata) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_TAG_NOT_FOUND,
"tag '%s' is not registered",
g_type_name(tag));
}
return FALSE;
}
if(!gplate_library_lookup_function(name, &function, NULL, error))
return FALSE;
tdata->default_function = function;
return TRUE;
}
/**
* gplate_library_get_default_function_for_tag:
* @tag: The #GPlateTag.
* @error: The return address for any errors.
*
* Get's the default function for @tag.
*
* Return Value: On success, the #GType of the function is returned.
* On failure, #G_TYPE_INVALID is returned and @error is set.
*
* However, if a tag does not have a default function,
* #G_TYPE_INVALID will be returned. So it is recommended to
* pass @error and check if it's set before assuming a return
* value of #G_TYPE_INVALID is an error.
*/
GType
gplate_library_get_default_function_for_tag(GType tag, GError **error) {
GPlateTagData *tdata = NULL;
gplate_library_lazy_init();
tdata = g_hash_table_lookup(tags, GSIZE_TO_POINTER(tag));
if(!tdata) {
if(error) {
*error = g_error_new(GPLATE_DOMAIN,
GPLATE_ERROR_LIBRARY_TAG_NOT_FOUND,
"tag '%s' is not registered",
g_type_name(tag));
}
return G_TYPE_INVALID;
}
return tdata->default_function;
}
/******************************************************************************
* For each stuff
*****************************************************************************/
typedef struct {
GPlateLibraryTagsForeachFunc func;
gpointer data;
} GPlateLibraryTagsForeachData;
static void
gplate_library_tags_foreach_helper(gpointer k, gpointer v, gpointer d) {
GPlateTagData *data = (GPlateTagData *)v;
GPlateLibraryTagsForeachData *fhd = (GPlateLibraryTagsForeachData *)d;
fhd->func(data->tag, data->prefix, data->suffix, fhd->data);
}
/**
* gplate_library_tags_foreach:
* @func: The callback function.
* @data: User data to pass to the callback function.
*
* Calls @func with each tag registered in the library.
*/
void
gplate_library_tags_foreach(GPlateLibraryTagsForeachFunc func, gpointer data) {
GPlateLibraryTagsForeachData d = { 0, };
gplate_library_lazy_init();
d.func = func;
d.data = data;
g_hash_table_foreach(tags, gplate_library_tags_foreach_helper, &d);
}