/*
 * descriptions.c: manipulate man page descriptions
 *
 * Copyright (C) 2002, 2003, 2006, 2007, 2008, 2009, 2010, 2011 Colin Watson.
 *
 * This file is part of man-db.
 *
 * man-db is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * man-db 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with man-db; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "gl_array_list.h"
#include "gl_xlist.h"
#include "xalloc.h"
#include "xstrndup.h"

#include "manconfig.h"

#include "debug.h"
#include "util.h"

#include "descriptions.h"

/* Free a page description. */
static void page_description_free (const void *value)
{
	struct page_description *desc = (struct page_description *) value;

	free (desc->name);
	free (desc->whatis);
	free (desc);
}

/* Parse the description in a whatis line returned by find_name() into a
 * list of names and whatis descriptions.
 */
gl_list_t parse_descriptions (const char *base, const char *whatis)
{
	const char *sep, *nextsep;
	gl_list_t descs;
	bool seen_base = false;

	descs = gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL,
	                              page_description_free, true);

	if (!whatis)
		return descs;

	sep = whatis;

	while (sep) {
		char *record;
		size_t length;
		const char *dash;
		char *names;
		const char *token;

		/* Use a while loop so that we skip over things like the
		 * result of double line breaks.
		 */
		while (*sep == 0x11 || *sep == ' ')
			++sep;
		nextsep = strchr (sep, 0x11);

		/* Get this record as a null-terminated string. */
		if (nextsep)
			length = (size_t) (nextsep - sep);
		else
			length = strlen (sep);
		if (length == 0)
			break;

		record = xstrndup (sep, length);
		debug ("record = '%s'\n", record);

		/* Split the record into name and whatis description. */
		dash = strstr (record, " - ");
		if (dash)
			names = xstrndup (record, dash - record);
		else if (!gl_list_size (descs))
			/* Some pages have a NAME section with just the page
			 * name and no whatis.  We might as well include
			 * this.
			 */
			names = xstrdup (record);
		else
			/* Once at least one record has been seen, further
			 * cases where there is no whatis usually amount to
			 * garbage following the useful records, and can
			 * cause problems due to false WHATIS_MAN entries in
			 * the database.  On the whole it seems best to
			 * ignore these.
			 */
			goto next;

		for (token = strtok (names, ","); token;
		     token = strtok (NULL, ",")) {
			char *name = trim_spaces (token);
			struct page_description *desc;

			/* Skip name tokens containing whitespace. They are
			 * almost never useful as manual page names.
			 */
			if (strpbrk (name, " \t") != NULL) {
				free (name);
				continue;
			}

			/* Allocate new description node. */
			desc = xmalloc (sizeof *desc);
			desc->name = name; /* steal memory */
			desc->whatis = dash ? trim_spaces (dash + 3) : NULL;
			gl_list_add_last (descs, desc);

			if (base && STREQ (base, desc->name))
				seen_base = true;
		}

		free (names);
next:
		free (record);

		sep = nextsep;
	}

	/* If it isn't there already, add the base name onto the returned
	 * list.
	 */
	if (base && !seen_base) {
		struct page_description *desc = xmalloc (sizeof *desc);

		desc->name = xstrdup (base);
		desc->whatis = NULL;
		if (gl_list_size (descs)) {
			const struct page_description *first =
			        gl_list_get_at (descs, 0);
			if (first->whatis)
				desc->whatis = xstrdup (first->whatis);
		}
		gl_list_add_last (descs, desc);
	}

	return descs;
}
