Patchwork [4/4] lib: add HTML logging backend

login
register
mail settings
Submitter Colin King
Date May 30, 2012, 6:40 p.m.
Message ID <1338403238-14029-5-git-send-email-colin.king@canonical.com>
Download mbox | patch
Permalink /patch/162055/
State Accepted
Headers show

Comments

Colin King - May 30, 2012, 6:40 p.m.
From: Colin Ian King <colin.king@canonical.com>

Signed-off-by: Colin Ian King <colin.king@canonical.com>
---
 src/lib/include/fwts_log.h   |    8 +-
 src/lib/src/Makefile.am      |    1 +
 src/lib/src/fwts_framework.c |   12 +-
 src/lib/src/fwts_log.c       |    3 +
 src/lib/src/fwts_log_html.c  |  297 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 313 insertions(+), 8 deletions(-)
 create mode 100644 src/lib/src/fwts_log_html.c
Ivan Hu - June 1, 2012, 6:35 a.m.
On 05/31/2012 02:40 AM, Colin King wrote:
> From: Colin Ian King<colin.king@canonical.com>
>
> Signed-off-by: Colin Ian King<colin.king@canonical.com>
> ---
>   src/lib/include/fwts_log.h   |    8 +-
>   src/lib/src/Makefile.am      |    1 +
>   src/lib/src/fwts_framework.c |   12 +-
>   src/lib/src/fwts_log.c       |    3 +
>   src/lib/src/fwts_log_html.c  |  297 ++++++++++++++++++++++++++++++++++++++++++
>   5 files changed, 313 insertions(+), 8 deletions(-)
>   create mode 100644 src/lib/src/fwts_log_html.c
>
> diff --git a/src/lib/include/fwts_log.h b/src/lib/include/fwts_log.h
> index 4e0f33a..a659baa 100644
> --- a/src/lib/include/fwts_log.h
> +++ b/src/lib/include/fwts_log.h
> @@ -63,6 +63,7 @@ typedef enum {
>   	LOG_TYPE_PLAINTEXT  = 0x00000001,
>   	LOG_TYPE_JSON       = 0x00000002,
>   	LOG_TYPE_XML        = 0x00000003,
> +	LOG_TYPE_HTML       = 0x00000004,
>   } fwts_log_type;
>
>   typedef struct log_t {
> @@ -84,9 +85,10 @@ typedef struct fwts_log_ops_t {
>   	void (*close)(fwts_log *);
>   } fwts_log_ops;
>
> -fwts_log_ops fwts_log_plaintext_ops;
> -fwts_log_ops fwts_log_json_ops;
> -fwts_log_ops fwts_log_xml_ops;
> +extern fwts_log_ops fwts_log_plaintext_ops;
> +extern fwts_log_ops fwts_log_json_ops;
> +extern fwts_log_ops fwts_log_xml_ops;
> +extern fwts_log_ops fwts_log_html_ops;
>
>   extern fwts_log_field fwts_log_filter;
>   extern const char *fwts_log_format;
> diff --git a/src/lib/src/Makefile.am b/src/lib/src/Makefile.am
> index acea9cb..f846621 100644
> --- a/src/lib/src/Makefile.am
> +++ b/src/lib/src/Makefile.am
> @@ -40,6 +40,7 @@ libfwts_la_SOURCES = \
>   	fwts_log_plaintext.c \
>   	fwts_log_json.c \
>   	fwts_log_xml.c \
> +	fwts_log_html.c \
>   	fwts_memorymap.c \
>   	fwts_microcode.c \
>   	fwts_mmap.c \
> diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c
> index 3f54fdb..721f4de 100644
> --- a/src/lib/src/fwts_framework.c
> +++ b/src/lib/src/fwts_framework.c
> @@ -76,7 +76,7 @@ static fwts_option fwts_framework_options[] = {
>   	{ "json-data-path", 	"j:", 1, "Specify path to fwts json data files - default is /usr/share/fwts." },
>   	{ "lp-tags-log", 	"",   0, "Output LaunchPad bug tags in results log." },
>   	{ "disassemble-aml", 	"",   0, "Disassemble AML from DSDT and SSDT tables." },
> -	{ "log-type",		"",   1, "Specify log type (plaintext, json or xml)." },
> +	{ "log-type",		"",   1, "Specify log type (plaintext, json, html or xml)." },
>   	{ NULL, NULL, 0, NULL }
>   };
>
> @@ -672,7 +672,7 @@ void fwts_framework_log(fwts_framework *fw,
>   	case LOG_ADVICE:
>   		fwts_log_nl(fw);
>   		snprintf(prefix, sizeof(prefix), "%s: ", str);
> -		fwts_log_printf(fw->results, LOG_ADVICE, level, str, label, prefix, "%s", buffer);
> +		fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
>   		fwts_log_nl(fw);
>   		break;
>   	case LOG_FAILED:
> @@ -680,7 +680,7 @@ void fwts_framework_log(fwts_framework *fw,
>   		fwts_summary_add(fw, fw->current_major_test->name, level, buffer);
>   		snprintf(prefix, sizeof(prefix), "%s [%s] %s: Test %d, ",
>   			str, fwts_log_level_to_str(level), label, fw->current_minor_test_num);
> -		fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
> +		fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
>   		break;
>   	case LOG_PASSED:
>   	case LOG_WARNING:
> @@ -688,7 +688,7 @@ void fwts_framework_log(fwts_framework *fw,
>   	case LOG_ABORTED:
>   		snprintf(prefix, sizeof(prefix), "%s: Test %d, ",
>   			str, fw->current_minor_test_num);
> -		fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
> +		fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
>   		break;
>   	case LOG_INFOONLY:
>   		break;	/* no-op */
> @@ -949,8 +949,10 @@ int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const ar
>   				fw->log_type = LOG_TYPE_JSON;
>   			else if (!strcmp(optarg, "xml"))
>   				fw->log_type = LOG_TYPE_XML;
> +			else if (!strcmp(optarg, "html"))
> +				fw->log_type = LOG_TYPE_HTML;
>   			else {
> -				fprintf(stderr, "--log-type can be either plaintext or json.\n");
> +				fprintf(stderr, "--log-type can be either plaintext, xml, html or json.\n");
>   				return FWTS_ERROR;
>   			}
>   			break;
> diff --git a/src/lib/src/fwts_log.c b/src/lib/src/fwts_log.c
> index c764d55..fea7c41 100644
> --- a/src/lib/src/fwts_log.c
> +++ b/src/lib/src/fwts_log.c
> @@ -432,6 +432,9 @@ fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode, f
>   	case LOG_TYPE_XML:
>   		newlog->ops =&fwts_log_xml_ops;
>   		break;
> +	case LOG_TYPE_HTML:
> +		newlog->ops =&fwts_log_html_ops;
> +		break;
>   	case LOG_TYPE_NONE:
>   	default:
>   		newlog->ops =&fwts_log_plaintext_ops;
> diff --git a/src/lib/src/fwts_log_html.c b/src/lib/src/fwts_log_html.c
> new file mode 100644
> index 0000000..25f81ef
> --- /dev/null
> +++ b/src/lib/src/fwts_log_html.c
> @@ -0,0 +1,297 @@
> +/*
> + * Copyright (C) 2010-2012 Canonical
> + *
> + * This program 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.
> + *
> + * 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.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
> + *
> + */
> +
> +#include<stdlib.h>
> +#include<stdio.h>
> +#include<stdarg.h>
> +#include<string.h>
> +#include<unistd.h>
> +#include<sys/ioctl.h>
> +#include<time.h>
> +
> +#include "fwts.h"
> +
> +#define MAX_HTML_STACK	(64)
> +#define HTML_INDENT	(2)
> +
> +typedef struct {
> +	const char *name;
> +} fwts_log_html_stack_t;
> +
> +static fwts_log_html_stack_t html_stack[MAX_HTML_STACK];
> +static int html_stack_index = 0;
> +
> +static void fwts_log_html(fwts_log *log, const char *fmt, ...)
> +{
> +	va_list args;
> +
> +	va_start(args, fmt);
> +
> +	fprintf(log->fp, "%*s", html_stack_index * HTML_INDENT, "");
> +	vfprintf(log->fp, fmt, args);
> +
> +	va_end(args);
> +}
> +
> +
> +/*
> + *  fwts_log_vprintf_html()
> + *	vprintf to a log
> + */
> +static int fwts_log_vprintf_html(fwts_log *log,
> +	const fwts_log_field field,
> +	const fwts_log_level level,
> +	const char *status,
> +	const char *label,
> +	const char *prefix,
> +	const char *fmt,
> +	va_list args)
> +{
> +	struct tm tm;
> +	time_t now;
> +	char *str;
> +	char *style;
> +	char *code_start;
> +	char *code_end;
> +
> +	if (!((field&  LOG_FIELD_MASK)&  fwts_log_filter))
> +		return 0;
> +
> +	if (field&  (LOG_NEWLINE | LOG_SEPARATOR | LOG_DEBUG))
> +		return 0;
> +
> +	time(&now);
> +	localtime_r(&now,&tm);
> +
> +	fwts_log_html(log, "<TR>\n");
> +
> +	if ((field&  LOG_FIELD_MASK) != LOG_HEADING) {
> +		fwts_log_html(log, "<TD>%2.2d/%2.2d/%-2.2d</TD>\n",
> +			tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100);
> +		fwts_log_html(log, "<TD>%2.2d:%2.2d:%2.2d</TD>\n",
> +			tm.tm_hour, tm.tm_min, tm.tm_sec);
> +	}
> +
> +	if (field&  LOG_VERBATUM) {
> +		code_start = "<PRE class=style_code>";
> +		code_end   = "</PRE>";
> +	} else {
> +		code_start = "";
> +		code_end   = "";
> +	}
> +
> +	switch (field&  LOG_FIELD_MASK) {
> +	case LOG_ERROR:
> +		fwts_log_html(log, "<TD class=style_error>Error</TD><TD COLSPAN=3>");
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "</TD>\n");
> +		break;
> +	case LOG_WARNING:
> +		fwts_log_html(log, "<TD class=style_error>Warning</TD><TD COLSPAN=3 class=style_advice_info>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +	case LOG_HEADING:
> +		fwts_log_html(log, "<TD COLSPAN=4 class=style_heading>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +	case LOG_INFO:
> +		fwts_log_html(log, "<TD></TD><TD COLSPAN=3 class=style_infos>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +	case LOG_PASSED:
> +		fwts_log_html(log, "<TD class=style_passed>PASSED</TD><TD>");
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "</TD>\n");
> +		break;
> +	case LOG_FAILED:
> +		switch (level) {
> +		case LOG_LEVEL_CRITICAL:
> +			style = " class=style_critical";
> +			break;
> +		case LOG_LEVEL_HIGH:
> +			style = " class=style_high";
> +			break;
> +		case LOG_LEVEL_MEDIUM:
> +			style = " class=style_medium";
> +			break;
> +		case LOG_LEVEL_LOW:
> +			style = " class=style_low";
> +			break;
> +		case LOG_LEVEL_INFO:
> +		case LOG_LEVEL_NONE:
> +		default:
> +			style = "";
> +			break;
> +		}
> +		str = fwts_log_level_to_str(level);
> +
> +		fwts_log_html(log, "<TD%s>%s [%s]</TD>\n", style, *status ? status : "", str);
> +
> +		fwts_log_html(log, "<TD>");
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "</TD>\n");
> +		break;
> +
> +	case LOG_SKIPPED:
> +		fwts_log_html(log, "<TD class=style_skipped>Skipped</TD><TD>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +
> +	case LOG_SUMMARY:
> +		fwts_log_html(log, "<TD></TD><TD COLSPAN=3 class=style_summary>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +
> +	case LOG_ADVICE:
> +		fwts_log_html(log, "<TD class=style_advice>Advice</TD><TD COLSPAN=3 class=style_advice_info>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	fwts_log_html(log, "</TR>\n");
> +	fflush(log->fp);
> +
> +	log->line_number++;
> +
> +	return 0;
> +}
> +
> +/*
> + *  fwts_log_underline_html()
> + *	write an underline across log, using character ch as the underline
> + */
> +static void fwts_log_underline_html(fwts_log *log, const int ch)
> +{
> +	/* No-op for html */
> +}
> +
> +/*
> + *  fwts_log_newline()
> + *	write newline to log
> + */
> +static void fwts_log_newline_html(fwts_log *log)
> +{
> +	/* No-op for html */
> +}
> +
> +static void fwts_log_section_begin_html(fwts_log *log, const char *name)
> +{
> +	html_stack[html_stack_index].name = name;
> +
> +	if (!strcmp(name, "summary")) {
> +		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4>Summary</TD></TR>\n");
> +	} else if (!strcmp(name, "heading")) {
> +		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4>Firmware Test Suite</TD></TR>\n");
> +	} else if (!strcmp(name, "subtest_info")) {
> +		fwts_log_html(log, "<TR><TD class=style_subtest COLSPAN=4></TD></TR>\n");
> +	} else if (!strcmp(name, "failure")) {
> +		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4></TD></TR>\n");
> +	}
> +
> +	fflush(log->fp);
> +
> +	if (html_stack_index<  MAX_HTML_STACK)
> +		html_stack_index++;
> +	else  {
> +		fprintf(stderr, "html log stack overflow pushing section %s.\n", name);
> +		exit(EXIT_FAILURE);
> +	}
> +}
> +
> +static void fwts_log_section_end_html(fwts_log *log)
> +{
> +	if (html_stack_index>  0) {
> +		html_stack_index--;
> +		fflush(log->fp);
> +	} else {
> +		fprintf(stderr, "html log stack underflow.\n");
> +		exit(EXIT_FAILURE);
> +	}
> +
> +}
> +
> +static void fwts_log_open_html(fwts_log *log)
> +{
> +	fwts_log_html(log, "<HTML>\n");
> +	fwts_log_html(log, "<HEAD>\n");
> +	fwts_log_html(log, "<TITLE>fwts log</TITLE>\n");
> +	fwts_log_html(log, "</HEAD>\n");
> +	fwts_log_html(log, "<BODY>\n");
> +	fwts_log_html(log, "<STYLE>\n");
> +	fwts_log_html(log,
> +		".style_critical { background-color: red; font-weight: bold; "
> +		"text-align: center; vertical-align: center  }\n"
> +		".style_high { background-color: orange; font-weight: bold; "
> +		"text-align: center; vertical-align: center  }\n"
> +		".style_medium { background-color: yellow; font-weight: bold; "
> +		"text-align: center; vertical-align: center  }\n"
> +		".style_low { background-color: #9acd32; font-weight: bold; "
> +		"text-align: center; vertical-align: center  }\n"
> +		".style_passed { background-color: green; font-weight: bold; "
> +		"text-align: center; vertical-align: center  }\n"
> +		".style_advice { text-align: center; vertical-align: center; "
> +		"font-weight: bold }\n"
> +		".style_advice_info { font-style: italic; font-weight: bold }"
> +		".style_skipped { background-color: wheat; text-align: center; "
> +		"vertical-align: center }\n"
> +		".style_heading { background-color: wheat; font-weight: bold; "
> +		"text-align: center }\n"
> +		".style_summary { font-weight: bold }\n"
> +		".style_error { background-color: orange; font-weight: bold; "
> +		"text-align: center; vertical-align: center }\n"
> +		".style_subtest { background-color: lightgray; }\n"
> +		".style_info { }\n"
> +		".style_code { font-family=sans-mono; line-height: 70% }\n");
> +	fwts_log_html(log, "</STYLE>\n");
> +	fflush(log->fp);
> +
> +	fwts_log_html(log, "<TABLE>\n");
> +	fwts_log_html(log, "</TR>\n");
> +
> +	fwts_log_section_begin_html(log, "fwts");
> +}
> +
> +static void fwts_log_close_html(fwts_log *log)
> +{
> +	fwts_log_section_end_html(log);
> +
> +	fwts_log_html(log, "</TABLE>\n");
> +	fwts_log_html(log, "</BODY>\n");
> +	fwts_log_html(log, "</HTML>\n");
> +	fflush(log->fp);
> +}
> +
> +fwts_log_ops fwts_log_html_ops = {
> +	.vprintf = 	 fwts_log_vprintf_html,
> +	.underline =	 fwts_log_underline_html,
> +	.newline =	 fwts_log_newline_html,
> +	.section_begin = fwts_log_section_begin_html,
> +	.section_end   = fwts_log_section_end_html,
> +	.open          = fwts_log_open_html,
> +	.close	       = fwts_log_close_html
> +};
Acked-by: Ivan Hu<ivan.hu@canonical.com>
Keng-Yu Lin - June 1, 2012, 8:48 a.m.
On Thu, May 31, 2012 at 2:40 AM, Colin King <colin.king@canonical.com> wrote:
> From: Colin Ian King <colin.king@canonical.com>
>
> Signed-off-by: Colin Ian King <colin.king@canonical.com>
> ---
>  src/lib/include/fwts_log.h   |    8 +-
>  src/lib/src/Makefile.am      |    1 +
>  src/lib/src/fwts_framework.c |   12 +-
>  src/lib/src/fwts_log.c       |    3 +
>  src/lib/src/fwts_log_html.c  |  297 ++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 313 insertions(+), 8 deletions(-)
>  create mode 100644 src/lib/src/fwts_log_html.c
>
> diff --git a/src/lib/include/fwts_log.h b/src/lib/include/fwts_log.h
> index 4e0f33a..a659baa 100644
> --- a/src/lib/include/fwts_log.h
> +++ b/src/lib/include/fwts_log.h
> @@ -63,6 +63,7 @@ typedef enum {
>        LOG_TYPE_PLAINTEXT  = 0x00000001,
>        LOG_TYPE_JSON       = 0x00000002,
>        LOG_TYPE_XML        = 0x00000003,
> +       LOG_TYPE_HTML       = 0x00000004,
>  } fwts_log_type;
>
>  typedef struct log_t {
> @@ -84,9 +85,10 @@ typedef struct fwts_log_ops_t {
>        void (*close)(fwts_log *);
>  } fwts_log_ops;
>
> -fwts_log_ops fwts_log_plaintext_ops;
> -fwts_log_ops fwts_log_json_ops;
> -fwts_log_ops fwts_log_xml_ops;
> +extern fwts_log_ops fwts_log_plaintext_ops;
> +extern fwts_log_ops fwts_log_json_ops;
> +extern fwts_log_ops fwts_log_xml_ops;
> +extern fwts_log_ops fwts_log_html_ops;
>
>  extern fwts_log_field fwts_log_filter;
>  extern const char *fwts_log_format;
> diff --git a/src/lib/src/Makefile.am b/src/lib/src/Makefile.am
> index acea9cb..f846621 100644
> --- a/src/lib/src/Makefile.am
> +++ b/src/lib/src/Makefile.am
> @@ -40,6 +40,7 @@ libfwts_la_SOURCES = \
>        fwts_log_plaintext.c \
>        fwts_log_json.c \
>        fwts_log_xml.c \
> +       fwts_log_html.c \
>        fwts_memorymap.c \
>        fwts_microcode.c \
>        fwts_mmap.c \
> diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c
> index 3f54fdb..721f4de 100644
> --- a/src/lib/src/fwts_framework.c
> +++ b/src/lib/src/fwts_framework.c
> @@ -76,7 +76,7 @@ static fwts_option fwts_framework_options[] = {
>        { "json-data-path",     "j:", 1, "Specify path to fwts json data files - default is /usr/share/fwts." },
>        { "lp-tags-log",        "",   0, "Output LaunchPad bug tags in results log." },
>        { "disassemble-aml",    "",   0, "Disassemble AML from DSDT and SSDT tables." },
> -       { "log-type",           "",   1, "Specify log type (plaintext, json or xml)." },
> +       { "log-type",           "",   1, "Specify log type (plaintext, json, html or xml)." },
>        { NULL, NULL, 0, NULL }
>  };
>
> @@ -672,7 +672,7 @@ void fwts_framework_log(fwts_framework *fw,
>        case LOG_ADVICE:
>                fwts_log_nl(fw);
>                snprintf(prefix, sizeof(prefix), "%s: ", str);
> -               fwts_log_printf(fw->results, LOG_ADVICE, level, str, label, prefix, "%s", buffer);
> +               fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
>                fwts_log_nl(fw);
>                break;
>        case LOG_FAILED:
> @@ -680,7 +680,7 @@ void fwts_framework_log(fwts_framework *fw,
>                fwts_summary_add(fw, fw->current_major_test->name, level, buffer);
>                snprintf(prefix, sizeof(prefix), "%s [%s] %s: Test %d, ",
>                        str, fwts_log_level_to_str(level), label, fw->current_minor_test_num);
> -               fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
> +               fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
>                break;
>        case LOG_PASSED:
>        case LOG_WARNING:
> @@ -688,7 +688,7 @@ void fwts_framework_log(fwts_framework *fw,
>        case LOG_ABORTED:
>                snprintf(prefix, sizeof(prefix), "%s: Test %d, ",
>                        str, fw->current_minor_test_num);
> -               fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
> +               fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
>                break;
>        case LOG_INFOONLY:
>                break;  /* no-op */
> @@ -949,8 +949,10 @@ int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const ar
>                                fw->log_type = LOG_TYPE_JSON;
>                        else if (!strcmp(optarg, "xml"))
>                                fw->log_type = LOG_TYPE_XML;
> +                       else if (!strcmp(optarg, "html"))
> +                               fw->log_type = LOG_TYPE_HTML;
>                        else {
> -                               fprintf(stderr, "--log-type can be either plaintext or json.\n");
> +                               fprintf(stderr, "--log-type can be either plaintext, xml, html or json.\n");
>                                return FWTS_ERROR;
>                        }
>                        break;
> diff --git a/src/lib/src/fwts_log.c b/src/lib/src/fwts_log.c
> index c764d55..fea7c41 100644
> --- a/src/lib/src/fwts_log.c
> +++ b/src/lib/src/fwts_log.c
> @@ -432,6 +432,9 @@ fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode, f
>        case LOG_TYPE_XML:
>                newlog->ops = &fwts_log_xml_ops;
>                break;
> +       case LOG_TYPE_HTML:
> +               newlog->ops = &fwts_log_html_ops;
> +               break;
>        case LOG_TYPE_NONE:
>        default:
>                newlog->ops = &fwts_log_plaintext_ops;
> diff --git a/src/lib/src/fwts_log_html.c b/src/lib/src/fwts_log_html.c
> new file mode 100644
> index 0000000..25f81ef
> --- /dev/null
> +++ b/src/lib/src/fwts_log_html.c
> @@ -0,0 +1,297 @@
> +/*
> + * Copyright (C) 2010-2012 Canonical
> + *
> + * This program 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.
> + *
> + * 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.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
> + *
> + */
> +
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <stdarg.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <time.h>
> +
> +#include "fwts.h"
> +
> +#define MAX_HTML_STACK (64)
> +#define HTML_INDENT    (2)
> +
> +typedef struct {
> +       const char *name;
> +} fwts_log_html_stack_t;
> +
> +static fwts_log_html_stack_t html_stack[MAX_HTML_STACK];
> +static int html_stack_index = 0;
> +
> +static void fwts_log_html(fwts_log *log, const char *fmt, ...)
> +{
> +       va_list args;
> +
> +       va_start(args, fmt);
> +
> +       fprintf(log->fp, "%*s", html_stack_index * HTML_INDENT, "");
> +       vfprintf(log->fp, fmt, args);
> +
> +       va_end(args);
> +}
> +
> +
> +/*
> + *  fwts_log_vprintf_html()
> + *     vprintf to a log
> + */
> +static int fwts_log_vprintf_html(fwts_log *log,
> +       const fwts_log_field field,
> +       const fwts_log_level level,
> +       const char *status,
> +       const char *label,
> +       const char *prefix,
> +       const char *fmt,
> +       va_list args)
> +{
> +       struct tm tm;
> +       time_t now;
> +       char *str;
> +       char *style;
> +       char *code_start;
> +       char *code_end;
> +
> +       if (!((field & LOG_FIELD_MASK) & fwts_log_filter))
> +               return 0;
> +
> +       if (field & (LOG_NEWLINE | LOG_SEPARATOR | LOG_DEBUG))
> +               return 0;
> +
> +       time(&now);
> +       localtime_r(&now, &tm);
> +
> +       fwts_log_html(log, "<TR>\n");
> +
> +       if ((field & LOG_FIELD_MASK) != LOG_HEADING) {
> +               fwts_log_html(log, "  <TD>%2.2d/%2.2d/%-2.2d</TD>\n",
> +                       tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100);
> +               fwts_log_html(log, "  <TD>%2.2d:%2.2d:%2.2d</TD>\n",
> +                       tm.tm_hour, tm.tm_min, tm.tm_sec);
> +       }
> +
> +       if (field & LOG_VERBATUM) {
> +               code_start = "<PRE class=style_code>";
> +               code_end   = "</PRE>";
> +       } else {
> +               code_start = "";
> +               code_end   = "";
> +       }
> +
> +       switch (field & LOG_FIELD_MASK) {
> +       case LOG_ERROR:
> +               fwts_log_html(log, "  <TD class=style_error>Error</TD><TD COLSPAN=3>");
> +               vfprintf(log->fp, fmt, args);
> +               fprintf(log->fp, "</TD>\n");
> +               break;
> +       case LOG_WARNING:
> +               fwts_log_html(log, "  <TD class=style_error>Warning</TD><TD COLSPAN=3 class=style_advice_info>%s", code_start);
> +               vfprintf(log->fp, fmt, args);
> +               fprintf(log->fp, "%s</TD>\n", code_end);
> +               break;
> +       case LOG_HEADING:
> +               fwts_log_html(log, "<TD COLSPAN=4 class=style_heading>%s", code_start);
> +               vfprintf(log->fp, fmt, args);
> +               fprintf(log->fp, "%s</TD>\n", code_end);
> +               break;
> +       case LOG_INFO:
> +               fwts_log_html(log, "  <TD></TD><TD COLSPAN=3 class=style_infos>%s", code_start);
> +               vfprintf(log->fp, fmt, args);
> +               fprintf(log->fp, "%s</TD>\n", code_end);
> +               break;
> +       case LOG_PASSED:
> +               fwts_log_html(log, "<TD class=style_passed>PASSED</TD><TD>");
> +               vfprintf(log->fp, fmt, args);
> +               fprintf(log->fp, "</TD>\n");
> +               break;
> +       case LOG_FAILED:
> +               switch (level) {
> +               case LOG_LEVEL_CRITICAL:
> +                       style = " class=style_critical";
> +                       break;
> +               case LOG_LEVEL_HIGH:
> +                       style = " class=style_high";
> +                       break;
> +               case LOG_LEVEL_MEDIUM:
> +                       style = " class=style_medium";
> +                       break;
> +               case LOG_LEVEL_LOW:
> +                       style = " class=style_low";
> +                       break;
> +               case LOG_LEVEL_INFO:
> +               case LOG_LEVEL_NONE:
> +               default:
> +                       style = "";
> +                       break;
> +               }
> +               str = fwts_log_level_to_str(level);
> +
> +               fwts_log_html(log, "  <TD%s>%s [%s]</TD>\n", style, *status ? status : "", str);
> +
> +               fwts_log_html(log, "  <TD>");
> +               vfprintf(log->fp, fmt, args);
> +               fprintf(log->fp, "</TD>\n");
> +               break;
> +
> +       case LOG_SKIPPED:
> +               fwts_log_html(log, "<TD class=style_skipped>Skipped</TD><TD>%s", code_start);
> +               vfprintf(log->fp, fmt, args);
> +               fprintf(log->fp, "%s</TD>\n", code_end);
> +               break;
> +
> +       case LOG_SUMMARY:
> +               fwts_log_html(log, "  <TD></TD><TD COLSPAN=3 class=style_summary>%s", code_start);
> +               vfprintf(log->fp, fmt, args);
> +               fprintf(log->fp, "%s</TD>\n", code_end);
> +               break;
> +
> +       case LOG_ADVICE:
> +               fwts_log_html(log, "  <TD class=style_advice>Advice</TD><TD COLSPAN=3 class=style_advice_info>%s", code_start);
> +               vfprintf(log->fp, fmt, args);
> +               fprintf(log->fp, "%s</TD>\n", code_end);
> +               break;
> +
> +       default:
> +               break;
> +       }
> +
> +       fwts_log_html(log, "</TR>\n");
> +       fflush(log->fp);
> +
> +       log->line_number++;
> +
> +       return 0;
> +}
> +
> +/*
> + *  fwts_log_underline_html()
> + *     write an underline across log, using character ch as the underline
> + */
> +static void fwts_log_underline_html(fwts_log *log, const int ch)
> +{
> +       /* No-op for html */
> +}
> +
> +/*
> + *  fwts_log_newline()
> + *     write newline to log
> + */
> +static void fwts_log_newline_html(fwts_log *log)
> +{
> +       /* No-op for html */
> +}
> +
> +static void fwts_log_section_begin_html(fwts_log *log, const char *name)
> +{
> +       html_stack[html_stack_index].name = name;
> +
> +       if (!strcmp(name, "summary")) {
> +               fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4>Summary</TD></TR>\n");
> +       } else if (!strcmp(name, "heading")) {
> +               fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4>Firmware Test Suite</TD></TR>\n");
> +       } else if (!strcmp(name, "subtest_info")) {
> +               fwts_log_html(log, "<TR><TD class=style_subtest COLSPAN=4></TD></TR>\n");
> +       } else if (!strcmp(name, "failure")) {
> +               fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4></TD></TR>\n");
> +       }
> +
> +       fflush(log->fp);
> +
> +       if (html_stack_index < MAX_HTML_STACK)
> +               html_stack_index++;
> +       else  {
> +               fprintf(stderr, "html log stack overflow pushing section %s.\n", name);
> +               exit(EXIT_FAILURE);
> +       }
> +}
> +
> +static void fwts_log_section_end_html(fwts_log *log)
> +{
> +       if (html_stack_index > 0) {
> +               html_stack_index--;
> +               fflush(log->fp);
> +       } else {
> +               fprintf(stderr, "html log stack underflow.\n");
> +               exit(EXIT_FAILURE);
> +       }
> +
> +}
> +
> +static void fwts_log_open_html(fwts_log *log)
> +{
> +       fwts_log_html(log, "<HTML>\n");
> +       fwts_log_html(log, "<HEAD>\n");
> +       fwts_log_html(log, "  <TITLE>fwts log</TITLE>\n");
> +       fwts_log_html(log, "</HEAD>\n");
> +       fwts_log_html(log, "<BODY>\n");
> +       fwts_log_html(log, "<STYLE>\n");
> +       fwts_log_html(log,
> +               ".style_critical { background-color: red; font-weight: bold; "
> +               "text-align: center; vertical-align: center  }\n"
> +               ".style_high { background-color: orange; font-weight: bold; "
> +               "text-align: center; vertical-align: center  }\n"
> +               ".style_medium { background-color: yellow; font-weight: bold; "
> +               "text-align: center; vertical-align: center  }\n"
> +               ".style_low { background-color: #9acd32; font-weight: bold; "
> +               "text-align: center; vertical-align: center  }\n"
> +               ".style_passed { background-color: green; font-weight: bold; "
> +               "text-align: center; vertical-align: center  }\n"
> +               ".style_advice { text-align: center; vertical-align: center; "
> +               "font-weight: bold }\n"
> +               ".style_advice_info { font-style: italic; font-weight: bold }"
> +               ".style_skipped { background-color: wheat; text-align: center; "
> +               "vertical-align: center }\n"
> +               ".style_heading { background-color: wheat; font-weight: bold; "
> +               "text-align: center }\n"
> +               ".style_summary { font-weight: bold }\n"
> +               ".style_error { background-color: orange; font-weight: bold; "
> +               "text-align: center; vertical-align: center }\n"
> +               ".style_subtest { background-color: lightgray; }\n"
> +               ".style_info { }\n"
> +               ".style_code { font-family=sans-mono; line-height: 70% }\n");
> +       fwts_log_html(log, "</STYLE>\n");
> +       fflush(log->fp);
> +
> +       fwts_log_html(log, "<TABLE>\n");
> +       fwts_log_html(log, "</TR>\n");
> +
> +       fwts_log_section_begin_html(log, "fwts");
> +}
> +
> +static void fwts_log_close_html(fwts_log *log)
> +{
> +       fwts_log_section_end_html(log);
> +
> +       fwts_log_html(log, "</TABLE>\n");
> +       fwts_log_html(log, "</BODY>\n");
> +       fwts_log_html(log, "</HTML>\n");
> +       fflush(log->fp);
> +}
> +
> +fwts_log_ops fwts_log_html_ops = {
> +       .vprintf =       fwts_log_vprintf_html,
> +       .underline =     fwts_log_underline_html,
> +       .newline =       fwts_log_newline_html,
> +       .section_begin = fwts_log_section_begin_html,
> +       .section_end   = fwts_log_section_end_html,
> +       .open          = fwts_log_open_html,
> +       .close         = fwts_log_close_html
> +};
> --
> 1.7.9.5
>
Acked-by: Keng-Yu Lin <kengyu@canonical.com>
Chris Van Hoof - June 11, 2012, 4:04 p.m.
On 05/30/2012 02:40 PM, Colin King wrote:
> From: Colin Ian King <colin.king@canonical.com>
> 
> Signed-off-by: Colin Ian King <colin.king@canonical.com>
> ---
>  src/lib/include/fwts_log.h   |    8 +-
>  src/lib/src/Makefile.am      |    1 +
>  src/lib/src/fwts_framework.c |   12 +-
>  src/lib/src/fwts_log.c       |    3 +
>  src/lib/src/fwts_log_html.c  |  297 ++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 313 insertions(+), 8 deletions(-)
>  create mode 100644 src/lib/src/fwts_log_html.c
> 
> diff --git a/src/lib/include/fwts_log.h b/src/lib/include/fwts_log.h
> index 4e0f33a..a659baa 100644
> --- a/src/lib/include/fwts_log.h
> +++ b/src/lib/include/fwts_log.h
> @@ -63,6 +63,7 @@ typedef enum {
>  	LOG_TYPE_PLAINTEXT  = 0x00000001,
>  	LOG_TYPE_JSON       = 0x00000002,
>  	LOG_TYPE_XML        = 0x00000003,
> +	LOG_TYPE_HTML       = 0x00000004,
>  } fwts_log_type;
>  
>  typedef struct log_t {
> @@ -84,9 +85,10 @@ typedef struct fwts_log_ops_t {
>  	void (*close)(fwts_log *);
>  } fwts_log_ops;
>  
> -fwts_log_ops fwts_log_plaintext_ops;
> -fwts_log_ops fwts_log_json_ops;
> -fwts_log_ops fwts_log_xml_ops;
> +extern fwts_log_ops fwts_log_plaintext_ops;
> +extern fwts_log_ops fwts_log_json_ops;
> +extern fwts_log_ops fwts_log_xml_ops;
> +extern fwts_log_ops fwts_log_html_ops;
>  
>  extern fwts_log_field fwts_log_filter;
>  extern const char *fwts_log_format;
> diff --git a/src/lib/src/Makefile.am b/src/lib/src/Makefile.am
> index acea9cb..f846621 100644
> --- a/src/lib/src/Makefile.am
> +++ b/src/lib/src/Makefile.am
> @@ -40,6 +40,7 @@ libfwts_la_SOURCES = \
>  	fwts_log_plaintext.c \
>  	fwts_log_json.c \
>  	fwts_log_xml.c \
> +	fwts_log_html.c \
>  	fwts_memorymap.c \
>  	fwts_microcode.c \
>  	fwts_mmap.c \
> diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c
> index 3f54fdb..721f4de 100644
> --- a/src/lib/src/fwts_framework.c
> +++ b/src/lib/src/fwts_framework.c
> @@ -76,7 +76,7 @@ static fwts_option fwts_framework_options[] = {
>  	{ "json-data-path", 	"j:", 1, "Specify path to fwts json data files - default is /usr/share/fwts." },
>  	{ "lp-tags-log", 	"",   0, "Output LaunchPad bug tags in results log." },
>  	{ "disassemble-aml", 	"",   0, "Disassemble AML from DSDT and SSDT tables." },
> -	{ "log-type",		"",   1, "Specify log type (plaintext, json or xml)." },
> +	{ "log-type",		"",   1, "Specify log type (plaintext, json, html or xml)." },
>  	{ NULL, NULL, 0, NULL }
>  };
>  
> @@ -672,7 +672,7 @@ void fwts_framework_log(fwts_framework *fw,
>  	case LOG_ADVICE:
>  		fwts_log_nl(fw);
>  		snprintf(prefix, sizeof(prefix), "%s: ", str);
> -		fwts_log_printf(fw->results, LOG_ADVICE, level, str, label, prefix, "%s", buffer);
> +		fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
>  		fwts_log_nl(fw);
>  		break;
>  	case LOG_FAILED:
> @@ -680,7 +680,7 @@ void fwts_framework_log(fwts_framework *fw,
>  		fwts_summary_add(fw, fw->current_major_test->name, level, buffer);
>  		snprintf(prefix, sizeof(prefix), "%s [%s] %s: Test %d, ",
>  			str, fwts_log_level_to_str(level), label, fw->current_minor_test_num);
> -		fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
> +		fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
>  		break;
>  	case LOG_PASSED:
>  	case LOG_WARNING:
> @@ -688,7 +688,7 @@ void fwts_framework_log(fwts_framework *fw,
>  	case LOG_ABORTED:
>  		snprintf(prefix, sizeof(prefix), "%s: Test %d, ",
>  			str, fw->current_minor_test_num);
> -		fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
> +		fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
>  		break;
>  	case LOG_INFOONLY:
>  		break;	/* no-op */
> @@ -949,8 +949,10 @@ int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const ar
>  				fw->log_type = LOG_TYPE_JSON;
>  			else if (!strcmp(optarg, "xml"))
>  				fw->log_type = LOG_TYPE_XML;
> +			else if (!strcmp(optarg, "html"))
> +				fw->log_type = LOG_TYPE_HTML;
>  			else {
> -				fprintf(stderr, "--log-type can be either plaintext or json.\n");
> +				fprintf(stderr, "--log-type can be either plaintext, xml, html or json.\n");
>  				return FWTS_ERROR;
>  			}
>  			break;
> diff --git a/src/lib/src/fwts_log.c b/src/lib/src/fwts_log.c
> index c764d55..fea7c41 100644
> --- a/src/lib/src/fwts_log.c
> +++ b/src/lib/src/fwts_log.c
> @@ -432,6 +432,9 @@ fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode, f
>  	case LOG_TYPE_XML:
>  		newlog->ops = &fwts_log_xml_ops;
>  		break;
> +	case LOG_TYPE_HTML:
> +		newlog->ops = &fwts_log_html_ops;
> +		break;
>  	case LOG_TYPE_NONE:
>  	default:
>  		newlog->ops = &fwts_log_plaintext_ops;
> diff --git a/src/lib/src/fwts_log_html.c b/src/lib/src/fwts_log_html.c
> new file mode 100644
> index 0000000..25f81ef
> --- /dev/null
> +++ b/src/lib/src/fwts_log_html.c
> @@ -0,0 +1,297 @@
> +/*
> + * Copyright (C) 2010-2012 Canonical
> + *
> + * This program 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.
> + *
> + * 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.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
> + *
> + */
> +
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <stdarg.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <time.h>
> +
> +#include "fwts.h"
> +
> +#define MAX_HTML_STACK	(64)
> +#define HTML_INDENT	(2)
> +
> +typedef struct {
> +	const char *name;
> +} fwts_log_html_stack_t;
> +
> +static fwts_log_html_stack_t html_stack[MAX_HTML_STACK];
> +static int html_stack_index = 0;
> +
> +static void fwts_log_html(fwts_log *log, const char *fmt, ...)
> +{
> +	va_list args;
> +
> +	va_start(args, fmt);
> +
> +	fprintf(log->fp, "%*s", html_stack_index * HTML_INDENT, "");
> +	vfprintf(log->fp, fmt, args);
> +
> +	va_end(args);
> +}
> +
> +
> +/*
> + *  fwts_log_vprintf_html()
> + *	vprintf to a log
> + */
> +static int fwts_log_vprintf_html(fwts_log *log,
> +	const fwts_log_field field,
> +	const fwts_log_level level,
> +	const char *status,
> +	const char *label,
> +	const char *prefix,
> +	const char *fmt,
> +	va_list args)
> +{
> +	struct tm tm;
> +	time_t now;
> +	char *str;
> +	char *style;
> +	char *code_start;
> +	char *code_end;
> +
> +	if (!((field & LOG_FIELD_MASK) & fwts_log_filter))
> +		return 0;
> +
> +	if (field & (LOG_NEWLINE | LOG_SEPARATOR | LOG_DEBUG))
> +		return 0;
> +
> +	time(&now);
> +	localtime_r(&now, &tm);
> +
> +	fwts_log_html(log, "<TR>\n");
> +
> +	if ((field & LOG_FIELD_MASK) != LOG_HEADING) {
> +		fwts_log_html(log, "  <TD>%2.2d/%2.2d/%-2.2d</TD>\n",
> +			tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100);
> +		fwts_log_html(log, "  <TD>%2.2d:%2.2d:%2.2d</TD>\n",
> +			tm.tm_hour, tm.tm_min, tm.tm_sec);
> +	}
> +
> +	if (field & LOG_VERBATUM) {
> +		code_start = "<PRE class=style_code>";
> +		code_end   = "</PRE>";
> +	} else {
> +		code_start = "";
> +		code_end   = "";
> +	}
> +
> +	switch (field & LOG_FIELD_MASK) {
> +	case LOG_ERROR:
> +		fwts_log_html(log, "  <TD class=style_error>Error</TD><TD COLSPAN=3>");
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "</TD>\n");
> +		break;
> +	case LOG_WARNING:
> +		fwts_log_html(log, "  <TD class=style_error>Warning</TD><TD COLSPAN=3 class=style_advice_info>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +	case LOG_HEADING:
> +		fwts_log_html(log, "<TD COLSPAN=4 class=style_heading>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +	case LOG_INFO:
> +		fwts_log_html(log, "  <TD></TD><TD COLSPAN=3 class=style_infos>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +	case LOG_PASSED:
> +		fwts_log_html(log, "<TD class=style_passed>PASSED</TD><TD>");
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "</TD>\n");
> +		break;
> +	case LOG_FAILED:
> +		switch (level) {
> +		case LOG_LEVEL_CRITICAL:
> +			style = " class=style_critical";
> +			break;
> +		case LOG_LEVEL_HIGH:
> +			style = " class=style_high";
> +			break;
> +		case LOG_LEVEL_MEDIUM:
> +			style = " class=style_medium";
> +			break;
> +		case LOG_LEVEL_LOW:
> +			style = " class=style_low";
> +			break;
> +		case LOG_LEVEL_INFO:
> +		case LOG_LEVEL_NONE:
> +		default:
> +			style = "";
> +			break;
> +		}
> +		str = fwts_log_level_to_str(level);
> +
> +		fwts_log_html(log, "  <TD%s>%s [%s]</TD>\n", style, *status ? status : "", str);
> +
> +		fwts_log_html(log, "  <TD>");
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "</TD>\n");
> +		break;
> +
> +	case LOG_SKIPPED:
> +		fwts_log_html(log, "<TD class=style_skipped>Skipped</TD><TD>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +
> +	case LOG_SUMMARY:
> +		fwts_log_html(log, "  <TD></TD><TD COLSPAN=3 class=style_summary>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +
> +	case LOG_ADVICE:
> +		fwts_log_html(log, "  <TD class=style_advice>Advice</TD><TD COLSPAN=3 class=style_advice_info>%s", code_start);
> +		vfprintf(log->fp, fmt, args);
> +		fprintf(log->fp, "%s</TD>\n", code_end);
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	fwts_log_html(log, "</TR>\n");
> +	fflush(log->fp);
> +
> +	log->line_number++;
> +
> +	return 0;
> +}
> +
> +/*
> + *  fwts_log_underline_html()
> + *	write an underline across log, using character ch as the underline
> + */
> +static void fwts_log_underline_html(fwts_log *log, const int ch)
> +{
> +	/* No-op for html */
> +}
> +
> +/*
> + *  fwts_log_newline()
> + *	write newline to log
> + */
> +static void fwts_log_newline_html(fwts_log *log)
> +{
> +	/* No-op for html */
> +}
> +
> +static void fwts_log_section_begin_html(fwts_log *log, const char *name)
> +{
> +	html_stack[html_stack_index].name = name;
> +
> +	if (!strcmp(name, "summary")) {
> +		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4>Summary</TD></TR>\n");
> +	} else if (!strcmp(name, "heading")) {
> +		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4>Firmware Test Suite</TD></TR>\n");
> +	} else if (!strcmp(name, "subtest_info")) {
> +		fwts_log_html(log, "<TR><TD class=style_subtest COLSPAN=4></TD></TR>\n");
> +	} else if (!strcmp(name, "failure")) {
> +		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4></TD></TR>\n");
> +	}
> +
> +	fflush(log->fp);
> +
> +	if (html_stack_index < MAX_HTML_STACK)
> +		html_stack_index++;
> +	else  {
> +		fprintf(stderr, "html log stack overflow pushing section %s.\n", name);
> +		exit(EXIT_FAILURE);
> +	}
> +}
> +
> +static void fwts_log_section_end_html(fwts_log *log)
> +{
> +	if (html_stack_index > 0) {
> +		html_stack_index--;
> +		fflush(log->fp);
> +	} else {
> +		fprintf(stderr, "html log stack underflow.\n");
> +		exit(EXIT_FAILURE);
> +	}
> +
> +}
> +
> +static void fwts_log_open_html(fwts_log *log)
> +{
> +	fwts_log_html(log, "<HTML>\n");
> +	fwts_log_html(log, "<HEAD>\n");
> +	fwts_log_html(log, "  <TITLE>fwts log</TITLE>\n");
> +	fwts_log_html(log, "</HEAD>\n");
> +	fwts_log_html(log, "<BODY>\n");
> +	fwts_log_html(log, "<STYLE>\n");
> +	fwts_log_html(log,
> +		".style_critical { background-color: red; font-weight: bold; "
> +		"text-align: center; vertical-align: center  }\n"
> +		".style_high { background-color: orange; font-weight: bold; "
> +		"text-align: center; vertical-align: center  }\n"
> +		".style_medium { background-color: yellow; font-weight: bold; "
> +		"text-align: center; vertical-align: center  }\n"
> +		".style_low { background-color: #9acd32; font-weight: bold; "
> +		"text-align: center; vertical-align: center  }\n"
> +		".style_passed { background-color: green; font-weight: bold; "
> +		"text-align: center; vertical-align: center  }\n"
> +		".style_advice { text-align: center; vertical-align: center; "
> +		"font-weight: bold }\n"
> +		".style_advice_info { font-style: italic; font-weight: bold }"
> +		".style_skipped { background-color: wheat; text-align: center; "
> +		"vertical-align: center }\n"
> +		".style_heading { background-color: wheat; font-weight: bold; "
> +		"text-align: center }\n"
> +		".style_summary { font-weight: bold }\n"
> +		".style_error { background-color: orange; font-weight: bold; "
> +		"text-align: center; vertical-align: center }\n"
> +		".style_subtest { background-color: lightgray; }\n"
> +		".style_info { }\n"
> +		".style_code { font-family=sans-mono; line-height: 70% }\n");
> +	fwts_log_html(log, "</STYLE>\n");
> +	fflush(log->fp);
> +
> +	fwts_log_html(log, "<TABLE>\n");
> +	fwts_log_html(log, "</TR>\n");
> +
> +	fwts_log_section_begin_html(log, "fwts");
> +}
> +
> +static void fwts_log_close_html(fwts_log *log)
> +{
> +	fwts_log_section_end_html(log);
> +
> +	fwts_log_html(log, "</TABLE>\n");
> +	fwts_log_html(log, "</BODY>\n");
> +	fwts_log_html(log, "</HTML>\n");
> +	fflush(log->fp);
> +}
> +
> +fwts_log_ops fwts_log_html_ops = {
> +	.vprintf = 	 fwts_log_vprintf_html,
> +	.underline =	 fwts_log_underline_html,
> +	.newline =	 fwts_log_newline_html,
> +	.section_begin = fwts_log_section_begin_html,
> +	.section_end   = fwts_log_section_end_html,
> +	.open          = fwts_log_open_html,
> +	.close	       = fwts_log_close_html
> +};

Tested-by: Chris Van Hoof <vanhoof@canonical.com>

Patch

diff --git a/src/lib/include/fwts_log.h b/src/lib/include/fwts_log.h
index 4e0f33a..a659baa 100644
--- a/src/lib/include/fwts_log.h
+++ b/src/lib/include/fwts_log.h
@@ -63,6 +63,7 @@  typedef enum {
 	LOG_TYPE_PLAINTEXT  = 0x00000001,
 	LOG_TYPE_JSON       = 0x00000002,
 	LOG_TYPE_XML        = 0x00000003,
+	LOG_TYPE_HTML       = 0x00000004,
 } fwts_log_type;
 
 typedef struct log_t {
@@ -84,9 +85,10 @@  typedef struct fwts_log_ops_t {
 	void (*close)(fwts_log *);
 } fwts_log_ops;
 
-fwts_log_ops fwts_log_plaintext_ops;
-fwts_log_ops fwts_log_json_ops;
-fwts_log_ops fwts_log_xml_ops;
+extern fwts_log_ops fwts_log_plaintext_ops;
+extern fwts_log_ops fwts_log_json_ops;
+extern fwts_log_ops fwts_log_xml_ops;
+extern fwts_log_ops fwts_log_html_ops;
 
 extern fwts_log_field fwts_log_filter;
 extern const char *fwts_log_format;
diff --git a/src/lib/src/Makefile.am b/src/lib/src/Makefile.am
index acea9cb..f846621 100644
--- a/src/lib/src/Makefile.am
+++ b/src/lib/src/Makefile.am
@@ -40,6 +40,7 @@  libfwts_la_SOURCES = \
 	fwts_log_plaintext.c \
 	fwts_log_json.c \
 	fwts_log_xml.c \
+	fwts_log_html.c \
 	fwts_memorymap.c \
 	fwts_microcode.c \
 	fwts_mmap.c \
diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c
index 3f54fdb..721f4de 100644
--- a/src/lib/src/fwts_framework.c
+++ b/src/lib/src/fwts_framework.c
@@ -76,7 +76,7 @@  static fwts_option fwts_framework_options[] = {
 	{ "json-data-path", 	"j:", 1, "Specify path to fwts json data files - default is /usr/share/fwts." },
 	{ "lp-tags-log", 	"",   0, "Output LaunchPad bug tags in results log." },
 	{ "disassemble-aml", 	"",   0, "Disassemble AML from DSDT and SSDT tables." },
-	{ "log-type",		"",   1, "Specify log type (plaintext, json or xml)." },
+	{ "log-type",		"",   1, "Specify log type (plaintext, json, html or xml)." },
 	{ NULL, NULL, 0, NULL }
 };
 
@@ -672,7 +672,7 @@  void fwts_framework_log(fwts_framework *fw,
 	case LOG_ADVICE:
 		fwts_log_nl(fw);
 		snprintf(prefix, sizeof(prefix), "%s: ", str);
-		fwts_log_printf(fw->results, LOG_ADVICE, level, str, label, prefix, "%s", buffer);
+		fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
 		fwts_log_nl(fw);
 		break;
 	case LOG_FAILED:
@@ -680,7 +680,7 @@  void fwts_framework_log(fwts_framework *fw,
 		fwts_summary_add(fw, fw->current_major_test->name, level, buffer);
 		snprintf(prefix, sizeof(prefix), "%s [%s] %s: Test %d, ",
 			str, fwts_log_level_to_str(level), label, fw->current_minor_test_num);
-		fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
+		fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
 		break;
 	case LOG_PASSED:
 	case LOG_WARNING:
@@ -688,7 +688,7 @@  void fwts_framework_log(fwts_framework *fw,
 	case LOG_ABORTED:
 		snprintf(prefix, sizeof(prefix), "%s: Test %d, ",
 			str, fw->current_minor_test_num);
-		fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
+		fwts_log_printf(fw->results, field, level, str, label, prefix, "%s", buffer);
 		break;
 	case LOG_INFOONLY:
 		break;	/* no-op */
@@ -949,8 +949,10 @@  int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const ar
 				fw->log_type = LOG_TYPE_JSON;
 			else if (!strcmp(optarg, "xml"))
 				fw->log_type = LOG_TYPE_XML;
+			else if (!strcmp(optarg, "html"))
+				fw->log_type = LOG_TYPE_HTML;
 			else {
-				fprintf(stderr, "--log-type can be either plaintext or json.\n");
+				fprintf(stderr, "--log-type can be either plaintext, xml, html or json.\n");
 				return FWTS_ERROR;
 			}
 			break;
diff --git a/src/lib/src/fwts_log.c b/src/lib/src/fwts_log.c
index c764d55..fea7c41 100644
--- a/src/lib/src/fwts_log.c
+++ b/src/lib/src/fwts_log.c
@@ -432,6 +432,9 @@  fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode, f
 	case LOG_TYPE_XML:
 		newlog->ops = &fwts_log_xml_ops;
 		break;
+	case LOG_TYPE_HTML:
+		newlog->ops = &fwts_log_html_ops;
+		break;
 	case LOG_TYPE_NONE:
 	default:
 		newlog->ops = &fwts_log_plaintext_ops;
diff --git a/src/lib/src/fwts_log_html.c b/src/lib/src/fwts_log_html.c
new file mode 100644
index 0000000..25f81ef
--- /dev/null
+++ b/src/lib/src/fwts_log_html.c
@@ -0,0 +1,297 @@ 
+/*
+ * Copyright (C) 2010-2012 Canonical
+ *
+ * This program 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.
+ *
+ * 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.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <time.h>
+
+#include "fwts.h"
+
+#define MAX_HTML_STACK	(64)
+#define HTML_INDENT	(2)
+
+typedef struct {
+	const char *name;
+} fwts_log_html_stack_t;
+
+static fwts_log_html_stack_t html_stack[MAX_HTML_STACK];
+static int html_stack_index = 0;
+
+static void fwts_log_html(fwts_log *log, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+
+	fprintf(log->fp, "%*s", html_stack_index * HTML_INDENT, "");
+	vfprintf(log->fp, fmt, args);
+
+	va_end(args);
+}
+
+
+/*
+ *  fwts_log_vprintf_html()
+ *	vprintf to a log
+ */
+static int fwts_log_vprintf_html(fwts_log *log,
+	const fwts_log_field field,
+	const fwts_log_level level,
+	const char *status,
+	const char *label,
+	const char *prefix,
+	const char *fmt,
+	va_list args)
+{
+	struct tm tm;
+	time_t now;
+	char *str;
+	char *style;
+	char *code_start;
+	char *code_end;
+
+	if (!((field & LOG_FIELD_MASK) & fwts_log_filter))
+		return 0;
+
+	if (field & (LOG_NEWLINE | LOG_SEPARATOR | LOG_DEBUG))
+		return 0;
+
+	time(&now);
+	localtime_r(&now, &tm);
+
+	fwts_log_html(log, "<TR>\n");
+
+	if ((field & LOG_FIELD_MASK) != LOG_HEADING) {
+		fwts_log_html(log, "  <TD>%2.2d/%2.2d/%-2.2d</TD>\n",
+			tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100);
+		fwts_log_html(log, "  <TD>%2.2d:%2.2d:%2.2d</TD>\n",
+			tm.tm_hour, tm.tm_min, tm.tm_sec);
+	}
+
+	if (field & LOG_VERBATUM) {
+		code_start = "<PRE class=style_code>";
+		code_end   = "</PRE>";
+	} else {
+		code_start = "";
+		code_end   = "";
+	}
+
+	switch (field & LOG_FIELD_MASK) {
+	case LOG_ERROR:
+		fwts_log_html(log, "  <TD class=style_error>Error</TD><TD COLSPAN=3>");
+		vfprintf(log->fp, fmt, args);
+		fprintf(log->fp, "</TD>\n");
+		break;
+	case LOG_WARNING:
+		fwts_log_html(log, "  <TD class=style_error>Warning</TD><TD COLSPAN=3 class=style_advice_info>%s", code_start);
+		vfprintf(log->fp, fmt, args);
+		fprintf(log->fp, "%s</TD>\n", code_end);
+		break;
+	case LOG_HEADING:
+		fwts_log_html(log, "<TD COLSPAN=4 class=style_heading>%s", code_start);
+		vfprintf(log->fp, fmt, args);
+		fprintf(log->fp, "%s</TD>\n", code_end);
+		break;
+	case LOG_INFO:
+		fwts_log_html(log, "  <TD></TD><TD COLSPAN=3 class=style_infos>%s", code_start);
+		vfprintf(log->fp, fmt, args);
+		fprintf(log->fp, "%s</TD>\n", code_end);
+		break;
+	case LOG_PASSED:
+		fwts_log_html(log, "<TD class=style_passed>PASSED</TD><TD>");
+		vfprintf(log->fp, fmt, args);
+		fprintf(log->fp, "</TD>\n");
+		break;
+	case LOG_FAILED:
+		switch (level) {
+		case LOG_LEVEL_CRITICAL:
+			style = " class=style_critical";
+			break;
+		case LOG_LEVEL_HIGH:
+			style = " class=style_high";
+			break;
+		case LOG_LEVEL_MEDIUM:
+			style = " class=style_medium";
+			break;
+		case LOG_LEVEL_LOW:
+			style = " class=style_low";
+			break;
+		case LOG_LEVEL_INFO:
+		case LOG_LEVEL_NONE:
+		default:
+			style = "";
+			break;
+		}
+		str = fwts_log_level_to_str(level);
+
+		fwts_log_html(log, "  <TD%s>%s [%s]</TD>\n", style, *status ? status : "", str);
+
+		fwts_log_html(log, "  <TD>");
+		vfprintf(log->fp, fmt, args);
+		fprintf(log->fp, "</TD>\n");
+		break;
+
+	case LOG_SKIPPED:
+		fwts_log_html(log, "<TD class=style_skipped>Skipped</TD><TD>%s", code_start);
+		vfprintf(log->fp, fmt, args);
+		fprintf(log->fp, "%s</TD>\n", code_end);
+		break;
+
+	case LOG_SUMMARY:
+		fwts_log_html(log, "  <TD></TD><TD COLSPAN=3 class=style_summary>%s", code_start);
+		vfprintf(log->fp, fmt, args);
+		fprintf(log->fp, "%s</TD>\n", code_end);
+		break;
+
+	case LOG_ADVICE:
+		fwts_log_html(log, "  <TD class=style_advice>Advice</TD><TD COLSPAN=3 class=style_advice_info>%s", code_start);
+		vfprintf(log->fp, fmt, args);
+		fprintf(log->fp, "%s</TD>\n", code_end);
+		break;
+
+	default:
+		break;
+	}
+
+	fwts_log_html(log, "</TR>\n");
+	fflush(log->fp);
+
+	log->line_number++;
+
+	return 0;
+}
+
+/*
+ *  fwts_log_underline_html()
+ *	write an underline across log, using character ch as the underline
+ */
+static void fwts_log_underline_html(fwts_log *log, const int ch)
+{
+	/* No-op for html */
+}
+
+/*
+ *  fwts_log_newline()
+ *	write newline to log
+ */
+static void fwts_log_newline_html(fwts_log *log)
+{
+	/* No-op for html */
+}
+
+static void fwts_log_section_begin_html(fwts_log *log, const char *name)
+{
+	html_stack[html_stack_index].name = name;
+
+	if (!strcmp(name, "summary")) {
+		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4>Summary</TD></TR>\n");
+	} else if (!strcmp(name, "heading")) {
+		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4>Firmware Test Suite</TD></TR>\n");
+	} else if (!strcmp(name, "subtest_info")) {
+		fwts_log_html(log, "<TR><TD class=style_subtest COLSPAN=4></TD></TR>\n");
+	} else if (!strcmp(name, "failure")) {
+		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=4></TD></TR>\n");
+	}
+
+	fflush(log->fp);
+
+	if (html_stack_index < MAX_HTML_STACK)
+		html_stack_index++;
+	else  {
+		fprintf(stderr, "html log stack overflow pushing section %s.\n", name);
+		exit(EXIT_FAILURE);
+	}
+}
+
+static void fwts_log_section_end_html(fwts_log *log)
+{
+	if (html_stack_index > 0) {
+		html_stack_index--;
+		fflush(log->fp);
+	} else {
+		fprintf(stderr, "html log stack underflow.\n");
+		exit(EXIT_FAILURE);
+	}
+
+}
+
+static void fwts_log_open_html(fwts_log *log)
+{
+	fwts_log_html(log, "<HTML>\n");
+	fwts_log_html(log, "<HEAD>\n");
+	fwts_log_html(log, "  <TITLE>fwts log</TITLE>\n");
+	fwts_log_html(log, "</HEAD>\n");
+	fwts_log_html(log, "<BODY>\n");
+	fwts_log_html(log, "<STYLE>\n");
+	fwts_log_html(log,
+		".style_critical { background-color: red; font-weight: bold; "
+		"text-align: center; vertical-align: center  }\n"
+		".style_high { background-color: orange; font-weight: bold; "
+		"text-align: center; vertical-align: center  }\n"
+		".style_medium { background-color: yellow; font-weight: bold; "
+		"text-align: center; vertical-align: center  }\n"
+		".style_low { background-color: #9acd32; font-weight: bold; "
+		"text-align: center; vertical-align: center  }\n"
+		".style_passed { background-color: green; font-weight: bold; "
+		"text-align: center; vertical-align: center  }\n"
+		".style_advice { text-align: center; vertical-align: center; "
+		"font-weight: bold }\n"
+		".style_advice_info { font-style: italic; font-weight: bold }"
+		".style_skipped { background-color: wheat; text-align: center; "
+		"vertical-align: center }\n"
+		".style_heading { background-color: wheat; font-weight: bold; "
+		"text-align: center }\n"
+		".style_summary { font-weight: bold }\n"
+		".style_error { background-color: orange; font-weight: bold; "
+		"text-align: center; vertical-align: center }\n"
+		".style_subtest { background-color: lightgray; }\n"
+		".style_info { }\n"
+		".style_code { font-family=sans-mono; line-height: 70% }\n");
+	fwts_log_html(log, "</STYLE>\n");
+	fflush(log->fp);
+
+	fwts_log_html(log, "<TABLE>\n");
+	fwts_log_html(log, "</TR>\n");
+
+	fwts_log_section_begin_html(log, "fwts");
+}
+
+static void fwts_log_close_html(fwts_log *log)
+{
+	fwts_log_section_end_html(log);
+
+	fwts_log_html(log, "</TABLE>\n");
+	fwts_log_html(log, "</BODY>\n");
+	fwts_log_html(log, "</HTML>\n");
+	fflush(log->fp);
+}
+
+fwts_log_ops fwts_log_html_ops = {
+	.vprintf = 	 fwts_log_vprintf_html,
+	.underline =	 fwts_log_underline_html,
+	.newline =	 fwts_log_newline_html,
+	.section_begin = fwts_log_section_begin_html,
+	.section_end   = fwts_log_section_end_html,
+	.open          = fwts_log_open_html,
+	.close	       = fwts_log_close_html
+};