Fix bug #14: Add a configuration file
authorali <ali@juiblex.co.uk>
Fri Oct 25 11:15:18 2013 +0100 (2013-10-25)
changeset 102ff0aa9b1397a
parent 101 f44c530f80da
child 103 d22d8cd4f628
Fix bug #14: Add a configuration file
Makefile.am
bookloupe/bookloupe.c
bookloupe/bookloupe.h
sample.ini
test/bookloupe/Makefile.am
test/bookloupe/config-default.tst
test/bookloupe/config-internal.tst
test/bookloupe/config-override.tst
test/bookloupe/config-user.tst
test/compatibility/Makefile.am
test/compatibility/no-paranoid-typos.tst
test/compatibility/no-paranoid.tst
test/compatibility/paranoid-typos.tst
test/compatibility/paranoid.tst
test/harness/Makefile.am
test/harness/loupe-test.c
test/harness/testcase.c
test/harness/testcase.h
test/harness/testcaseio.c
test/harness/testcaseoutput.c
test/harness/testcaseoutput.h
     1.1 --- a/Makefile.am	Sat Oct 26 18:47:33 2013 +0100
     1.2 +++ b/Makefile.am	Fri Oct 25 11:15:18 2013 +0100
     1.3 @@ -1,1 +1,3 @@
     1.4  SUBDIRS=bl bookloupe test doc
     1.5 +
     1.6 +dist_pkgdata_DATA=sample.ini
     2.1 --- a/bookloupe/bookloupe.c	Sat Oct 26 18:47:33 2013 +0100
     2.2 +++ b/bookloupe/bookloupe.c	Fri Oct 25 11:15:18 2013 +0100
     2.3 @@ -128,35 +128,97 @@
     2.4  
     2.5  gboolean pswit[SWITNO];  /* program switches */
     2.6  
     2.7 +gboolean typo_compat,paranoid_compat;
     2.8 +
     2.9  static GOptionEntry options[]={
    2.10      { "dp", 'd', 0, G_OPTION_ARG_NONE, pswit+DP_SWITCH,
    2.11        "Ignore DP-specific markup", NULL },
    2.12 -    { "noecho", 'e', 0, G_OPTION_ARG_NONE, pswit+ECHO_SWITCH,
    2.13 +    { "no-dp", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE,
    2.14 +      G_OPTION_ARG_NONE, pswit+DP_SWITCH,
    2.15 +      "Don't ignore DP-specific markup", NULL },
    2.16 +    { "echo", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, pswit+ECHO_SWITCH,
    2.17 +      "Echo queried line", NULL },
    2.18 +    { "no-echo", 'e', G_OPTION_FLAG_REVERSE,
    2.19 +      G_OPTION_ARG_NONE, pswit+ECHO_SWITCH,
    2.20        "Don't echo queried line", NULL },
    2.21      { "squote", 's', 0, G_OPTION_ARG_NONE, pswit+SQUOTE_SWITCH,
    2.22        "Check single quotes", NULL },
    2.23 -    { "typo", 't', 0, G_OPTION_ARG_NONE, pswit+TYPO_SWITCH,
    2.24 +    { "no-squote", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE,
    2.25 +      G_OPTION_ARG_NONE, pswit+SQUOTE_SWITCH,
    2.26 +      "Don't check single quotes", NULL },
    2.27 +    { "typo", 0, 0, G_OPTION_ARG_NONE, pswit+TYPO_SWITCH,
    2.28        "Check common typos", NULL },
    2.29 +    { "no-typo", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE,
    2.30 +      G_OPTION_ARG_NONE, pswit+TYPO_SWITCH,
    2.31 +      "Don't check common typos", NULL },
    2.32      { "qpara", 'p', 0, G_OPTION_ARG_NONE, pswit+QPARA_SWITCH,
    2.33        "Require closure of quotes on every paragraph", NULL },
    2.34 -    { "relaxed", 'x', 0, G_OPTION_ARG_NONE, pswit+PARANOID_SWITCH,
    2.35 +    { "no-qpara", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE,
    2.36 +      G_OPTION_ARG_NONE, pswit+QPARA_SWITCH,
    2.37 +      "Don't require closure of quotes on every paragraph", NULL },
    2.38 +    { "paranoid", 0, G_OPTION_FLAG_HIDDEN,
    2.39 +      G_OPTION_ARG_NONE, pswit+PARANOID_SWITCH,
    2.40 +      "Enable paranoid querying of everything", NULL },
    2.41 +    { "no-paranoid", 0, G_OPTION_FLAG_REVERSE,
    2.42 +      G_OPTION_ARG_NONE, pswit+PARANOID_SWITCH,
    2.43        "Disable paranoid querying of everything", NULL },
    2.44 -    { "line-end", 'l', 0, G_OPTION_ARG_NONE, pswit+LINE_END_SWITCH,
    2.45 +    { "line-end", 0, G_OPTION_FLAG_HIDDEN,
    2.46 +      G_OPTION_ARG_NONE, pswit+LINE_END_SWITCH,
    2.47 +      "Enable line end checking", NULL },
    2.48 +    { "no-line-end", 'l', G_OPTION_FLAG_REVERSE,
    2.49 +      G_OPTION_ARG_NONE, pswit+LINE_END_SWITCH,
    2.50        "Disable line end checking", NULL },
    2.51      { "overview", 'o', 0, G_OPTION_ARG_NONE, pswit+OVERVIEW_SWITCH,
    2.52        "Overview: just show counts", NULL },
    2.53 +    { "no-overview", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE,
    2.54 +      G_OPTION_ARG_NONE, pswit+OVERVIEW_SWITCH,
    2.55 +      "Show individual warnings", NULL },
    2.56      { "stdout", 'y', 0, G_OPTION_ARG_NONE, pswit+STDOUT_SWITCH,
    2.57        "Output errors to stdout instead of stderr", NULL },
    2.58 +    { "no-stdout", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE,
    2.59 +      G_OPTION_ARG_NONE, pswit+STDOUT_SWITCH,
    2.60 +      "Output errors to stderr instead of stdout", NULL },
    2.61      { "header", 'h', 0, G_OPTION_ARG_NONE, pswit+HEADER_SWITCH,
    2.62        "Echo header fields", NULL },
    2.63 +    { "no-header", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE,
    2.64 +      G_OPTION_ARG_NONE, pswit+HEADER_SWITCH,
    2.65 +      "Don't echo header fields", NULL },
    2.66      { "markup", 'm', 0, G_OPTION_ARG_NONE, pswit+MARKUP_SWITCH,
    2.67        "Ignore markup in < >", NULL },
    2.68 +    { "no-markup", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE,
    2.69 +      G_OPTION_ARG_NONE, pswit+MARKUP_SWITCH,
    2.70 +      "No special handling for markup in < >", NULL },
    2.71      { "usertypo", 'u', 0, G_OPTION_ARG_NONE, pswit+USERTYPO_SWITCH,
    2.72        "Use file of user-defined typos", NULL },
    2.73 +    { "no-usertypo", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE,
    2.74 +      G_OPTION_ARG_NONE, pswit+USERTYPO_SWITCH,
    2.75 +      "Ignore file of user-defined typos", NULL },
    2.76 +    { "verbose", 'v', 0, G_OPTION_ARG_NONE, pswit+VERBOSE_SWITCH,
    2.77 +      "Verbose - list everything", NULL },
    2.78 +    { "no-verbose", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE,
    2.79 +      G_OPTION_ARG_NONE, pswit+VERBOSE_SWITCH,
    2.80 +      "Switch off verbose mode", NULL },
    2.81 +    { NULL }
    2.82 +};
    2.83 +
    2.84 +/*
    2.85 + * Options relating to configuration which make no sense from inside
    2.86 + * a configuration file.
    2.87 + */
    2.88 +
    2.89 +static GOptionEntry config_options[]={
    2.90      { "web", 'w', 0, G_OPTION_ARG_NONE, pswit+WEB_SWITCH,
    2.91        "Defaults for use on www upload", NULL },
    2.92 -    { "verbose", 'v', 0, G_OPTION_ARG_NONE, pswit+VERBOSE_SWITCH,
    2.93 -      "Verbose - list everything", NULL },
    2.94 +    { "dump-config", 0, 0, G_OPTION_ARG_NONE, pswit+DUMP_CONFIG_SWITCH,
    2.95 +      "Dump current config settings", NULL },
    2.96 +    { NULL }
    2.97 +};
    2.98 +
    2.99 +static GOptionEntry compatibility_options[]={
   2.100 +    { "toggle-typo", 't', 0, G_OPTION_ARG_NONE, &typo_compat,
   2.101 +      "Toggle checking for common typos", NULL },
   2.102 +    { "toggle-relaxed", 'x', 0, G_OPTION_ARG_NONE, &paranoid_compat,
   2.103 +      "Toggle both paranoid mode and common typos", NULL },
   2.104      { NULL }
   2.105  };
   2.106  
   2.107 @@ -200,31 +262,198 @@
   2.108  UINT saved_cp;
   2.109  #endif
   2.110  
   2.111 +GKeyFile *config;
   2.112 +
   2.113 +void config_file_update(GKeyFile *kf)
   2.114 +{
   2.115 +    int i;
   2.116 +    gboolean sw;
   2.117 +    for(i=0;options[i].long_name;i++)
   2.118 +    {
   2.119 +	if (g_str_has_prefix(options[i].long_name,"no-"))
   2.120 +	    continue;
   2.121 +	if (options[i].arg==G_OPTION_ARG_NONE)
   2.122 +	{
   2.123 +	    sw=*(gboolean *)options[i].arg_data;
   2.124 +	    if (options[i].flags&G_OPTION_FLAG_REVERSE)
   2.125 +		sw=!sw;
   2.126 +	    g_key_file_set_boolean(kf,"options",options[i].long_name,sw);
   2.127 +	}
   2.128 +	else
   2.129 +	    g_assert_not_reached();
   2.130 +    }
   2.131 +}
   2.132 +
   2.133 +void config_file_add_comments(GKeyFile *kf)
   2.134 +{
   2.135 +    int i;
   2.136 +    gchar *comment;
   2.137 +    g_key_file_set_comment(kf,NULL,NULL," Default configuration for bookloupe",
   2.138 +      NULL);
   2.139 +    for(i=0;options[i].long_name;i++)
   2.140 +    {
   2.141 +	if (g_str_has_prefix(options[i].long_name,"no-"))
   2.142 +	    continue;
   2.143 +	comment=g_strconcat(" ",options[i].description,NULL);
   2.144 +	g_key_file_set_comment(kf,"options",options[i].long_name,comment,NULL);
   2.145 +	g_free(comment);
   2.146 +    }
   2.147 +}
   2.148 +
   2.149 +void dump_config(void)
   2.150 +{
   2.151 +    gchar *s;
   2.152 +    if (config)
   2.153 +	config_file_update(config);
   2.154 +    else
   2.155 +    {
   2.156 +	config=g_key_file_new();
   2.157 +	config_file_update(config);
   2.158 +	config_file_add_comments(config);
   2.159 +    }
   2.160 +    s=g_key_file_to_data(config,NULL,NULL);
   2.161 +    if (s)
   2.162 +	g_print("%s",s);
   2.163 +    g_free(s);
   2.164 +}
   2.165 +
   2.166 +GKeyFile *read_config_file(gchar **full_path)
   2.167 +{
   2.168 +    int i;
   2.169 +    GError *err=NULL;
   2.170 +    gchar **search_dirs;
   2.171 +    gchar *path;
   2.172 +    const char *search_path;
   2.173 +    GKeyFile *kf;
   2.174 +    kf=g_key_file_new();
   2.175 +    search_path=g_getenv("BOOKLOUPE_CONFIG_PATH");
   2.176 +    if (search_path)
   2.177 +    {
   2.178 +#ifdef __WIN32__
   2.179 +	search_dirs=g_strsplit(search_path,";",0);
   2.180 +#else
   2.181 +	search_dirs=g_strsplit(search_path,":",0);
   2.182 +#endif
   2.183 +    }
   2.184 +    else
   2.185 +    {
   2.186 +	search_dirs=g_new(gchar *,4);
   2.187 +	search_dirs[0]=g_get_current_dir();
   2.188 +	search_dirs[1]=g_strdup(running_from);
   2.189 +	search_dirs[2]=g_strdup(g_get_user_config_dir());
   2.190 +	search_dirs[3]=NULL;
   2.191 +    }
   2.192 +    for(i=0;search_dirs[i];i++)
   2.193 +    {
   2.194 +	path=g_build_filename(search_dirs[i],"bookloupe.ini",NULL);
   2.195 +	if (g_key_file_load_from_file(kf,path,
   2.196 +	  G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS,&err))
   2.197 +	    break;
   2.198 +	if (!g_error_matches(err,G_FILE_ERROR,G_FILE_ERROR_NOENT))
   2.199 +	{
   2.200 +	    g_printerr("Bookloupe: Error reading %s\n",path);
   2.201 +	    g_printerr("%s\n",err->message);
   2.202 +	    exit(1);
   2.203 +	}
   2.204 +	g_clear_error(&err);
   2.205 +	g_free(path);
   2.206 +	path=NULL;
   2.207 +    }
   2.208 +    if (!search_dirs[i])
   2.209 +    {
   2.210 +	g_key_file_free(kf);
   2.211 +	kf=NULL;
   2.212 +    }
   2.213 +    g_strfreev(search_dirs);
   2.214 +    if (full_path && kf)
   2.215 +	*full_path=path;
   2.216 +    else
   2.217 +	g_free(path);
   2.218 +    return kf;
   2.219 +}
   2.220 +
   2.221 +void parse_config_file(void)
   2.222 +{
   2.223 +    int i,j;
   2.224 +    gchar *path;
   2.225 +    gchar **keys;
   2.226 +    gboolean sw;
   2.227 +    GError *err=NULL;
   2.228 +    config=read_config_file(&path);
   2.229 +    if (config)
   2.230 +	keys=g_key_file_get_keys(config,"options",NULL,NULL);
   2.231 +    else
   2.232 +	keys=NULL;
   2.233 +    if (keys)
   2.234 +    {
   2.235 +	for(i=0;keys[i];i++)
   2.236 +	{
   2.237 +	    for(j=0;options[j].long_name;j++)
   2.238 +	    {
   2.239 +		if (g_str_has_prefix(options[j].long_name,"no-"))
   2.240 +		    continue;
   2.241 +		else if (!strcmp(keys[i],options[j].long_name))
   2.242 +		{
   2.243 +		    if (options[j].arg==G_OPTION_ARG_NONE)
   2.244 +		    {
   2.245 +			sw=g_key_file_get_boolean(config,"options",keys[i],
   2.246 +			  &err);
   2.247 +			if (err)
   2.248 +			{
   2.249 +			    g_printerr("Bookloupe: %s: options.%s: %s\n",
   2.250 +			      path,keys[i],err->message);
   2.251 +			    g_clear_error(&err);
   2.252 +			}
   2.253 +			if (options[j].flags&G_OPTION_FLAG_REVERSE)
   2.254 +			    sw=!sw;
   2.255 +			*(gboolean *)options[j].arg_data=sw;
   2.256 +			break;
   2.257 +		    }
   2.258 +		    else
   2.259 +			g_assert_not_reached();
   2.260 +		}
   2.261 +	    }
   2.262 +	    if (!options[j].long_name)
   2.263 +		g_printerr("Bookloupe: %s: Unknown option \"%s\" ignored\n",
   2.264 +		  path,keys[i]);
   2.265 +	}
   2.266 +	g_strfreev(keys);
   2.267 +    }
   2.268 +    if (config)
   2.269 +	g_free(path);
   2.270 +}
   2.271 +
   2.272  void parse_options(int *argc,char ***argv)
   2.273  {
   2.274      GError *err=NULL;
   2.275      GOptionContext *context;
   2.276 +    GOptionGroup *compatibility;
   2.277      context=g_option_context_new(
   2.278 -      "file - looks for errors in Project Gutenberg(TM) etexts");
   2.279 +      "file - look for errors in Project Gutenberg(TM) etexts");
   2.280      g_option_context_add_main_entries(context,options,NULL);
   2.281 +    g_option_context_add_main_entries(context,config_options,NULL);
   2.282 +    compatibility=g_option_group_new("compatibility",
   2.283 +      "Options for Compatibility with Gutcheck:",
   2.284 +      "Show compatibility options",NULL,NULL);
   2.285 +    g_option_group_add_entries(compatibility,compatibility_options);
   2.286 +    g_option_context_add_group(context,compatibility);
   2.287 +    g_option_context_set_description(context,
   2.288 +      "For simplicity, only the switch options which reverse the\n"
   2.289 +      "default configuration are listed. In most cases, both vanilla\n"
   2.290 +      "and \"no-\" prefixed versions are available for use.");
   2.291      if (!g_option_context_parse(context,argc,argv,&err))
   2.292      {
   2.293  	g_printerr("Bookloupe: %s\n",err->message);
   2.294  	g_printerr("Use \"%s --help\" for help\n",(*argv)[0]);
   2.295  	exit(1);
   2.296      }
   2.297 -    /* Paranoid checking is turned OFF, not on, by its switch */
   2.298 -    pswit[PARANOID_SWITCH]=!pswit[PARANOID_SWITCH];
   2.299 -    if (pswit[PARANOID_SWITCH])
   2.300 -	/* if running in paranoid mode, typo checks default to enabled */
   2.301 +    if (typo_compat)
   2.302  	pswit[TYPO_SWITCH]=!pswit[TYPO_SWITCH];
   2.303 -    /* Line-end checking is turned OFF, not on, by its switch */
   2.304 -    pswit[LINE_END_SWITCH]=!pswit[LINE_END_SWITCH];
   2.305 -    /* Echoing is turned OFF, not on, by its switch */
   2.306 -    pswit[ECHO_SWITCH]=!pswit[ECHO_SWITCH];
   2.307 -    if (pswit[OVERVIEW_SWITCH])
   2.308 -	/* just print summary; don't echo */
   2.309 -	pswit[ECHO_SWITCH]=FALSE;
   2.310 +    if (paranoid_compat)
   2.311 +    {
   2.312 +	pswit[PARANOID_SWITCH]=!pswit[PARANOID_SWITCH];
   2.313 +	pswit[TYPO_SWITCH]=!pswit[TYPO_SWITCH];
   2.314 +    }
   2.315      /*
   2.316       * Web uploads - for the moment, this is really just a placeholder
   2.317       * until we decide what processing we really want to do on web uploads
   2.318 @@ -246,6 +475,14 @@
   2.319  	pswit[USERTYPO_SWITCH]=FALSE;
   2.320  	pswit[DP_SWITCH]=FALSE;
   2.321      }
   2.322 +    if (pswit[DUMP_CONFIG_SWITCH])
   2.323 +    {
   2.324 +	dump_config();
   2.325 +	exit(0);
   2.326 +    }
   2.327 +    if (pswit[OVERVIEW_SWITCH])
   2.328 +	/* just print summary; don't echo */
   2.329 +	pswit[ECHO_SWITCH]=FALSE;
   2.330      if (*argc<2)
   2.331      {
   2.332  	proghelp(context);
   2.333 @@ -388,6 +625,15 @@
   2.334      saved_cp=GetConsoleOutputCP();
   2.335  #endif
   2.336      running_from=g_path_get_dirname(argv[0]);
   2.337 +    /* Paranoid checking is turned OFF, not on, by its switch */
   2.338 +    pswit[PARANOID_SWITCH]=TRUE;
   2.339 +    /* if running in paranoid mode, typo checks default to enabled */
   2.340 +    pswit[TYPO_SWITCH]=TRUE;
   2.341 +    /* Line-end checking is turned OFF, not on, by its switch */
   2.342 +    pswit[LINE_END_SWITCH]=TRUE;
   2.343 +    /* Echoing is turned OFF, not on, by its switch */
   2.344 +    pswit[ECHO_SWITCH]=TRUE;
   2.345 +    parse_config_file();
   2.346      parse_options(&argc,&argv);
   2.347      if (pswit[USERTYPO_SWITCH])
   2.348  	read_user_scannos();
   2.349 @@ -428,6 +674,8 @@
   2.350      g_free(running_from);
   2.351      if (usertypo)
   2.352  	g_tree_unref(usertypo);
   2.353 +    if (config)
   2.354 +	g_key_file_free(config);
   2.355      return 0;
   2.356  }
   2.357  
     3.1 --- a/bookloupe/bookloupe.h	Sat Oct 26 18:47:33 2013 +0100
     3.2 +++ b/bookloupe/bookloupe.h	Fri Oct 25 11:15:18 2013 +0100
     3.3 @@ -55,6 +55,7 @@
     3.4      MARKUP_SWITCH,
     3.5      USERTYPO_SWITCH,
     3.6      DP_SWITCH,
     3.7 +    DUMP_CONFIG_SWITCH,
     3.8      SWITNO
     3.9  };
    3.10  
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/sample.ini	Fri Oct 25 11:15:18 2013 +0100
     4.3 @@ -0,0 +1,31 @@
     4.4 +# Default configuration for bookloupe
     4.5 +
     4.6 +[options]
     4.7 +# Ignore DP-specific markup
     4.8 +dp=false
     4.9 +# Echo queried line
    4.10 +echo=true
    4.11 +# Check single quotes
    4.12 +squote=false
    4.13 +# Check common typos
    4.14 +typo=true
    4.15 +# Require closure of quotes on every paragraph
    4.16 +qpara=false
    4.17 +# Enable paranoid querying of everything
    4.18 +paranoid=true
    4.19 +# Enable line end checking
    4.20 +line-end=true
    4.21 +# Overview: just show counts
    4.22 +overview=false
    4.23 +# Output errors to stdout instead of stderr
    4.24 +stdout=false
    4.25 +# Echo header fields
    4.26 +header=false
    4.27 +# Ignore markup in < >
    4.28 +markup=false
    4.29 +# Use file of user-defined typos
    4.30 +usertypo=false
    4.31 +# Defaults for use on www upload
    4.32 +web=false
    4.33 +# Verbose - list everything
    4.34 +verbose=false
     5.1 --- a/test/bookloupe/Makefile.am	Sat Oct 26 18:47:33 2013 +0100
     5.2 +++ b/test/bookloupe/Makefile.am	Fri Oct 25 11:15:18 2013 +0100
     5.3 @@ -1,6 +1,8 @@
     5.4  TESTS_ENVIRONMENT=BOOKLOUPE=../../bookloupe/bookloupe ../harness/loupe-test
     5.5  TESTS=non-ascii.tst long-line.tst curved-single-quotes.tst curved-quotes.tst \
     5.6  	runfox-quotes.tst curved-genitives.tst multi-line-illustration.tst \
     5.7 -	emdash.tst footnote-marker.tst unix-lineends.tst os9-lineends.tst
     5.8 +	emdash.tst config-internal.tst config-default.tst config-user.tst \
     5.9 +	config-override.tst footnote-marker.tst unix-lineends.tst \
    5.10 +	os9-lineends.tst
    5.11  
    5.12  dist_pkgdata_DATA=$(TESTS)
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/test/bookloupe/config-default.tst	Fri Oct 25 11:15:18 2013 +0100
     6.3 @@ -0,0 +1,62 @@
     6.4 +**************** OPTIONS ****************
     6.5 +--dump-config
     6.6 +**************** INPUT(bookloupe.ini) ****************
     6.7 +# Default configuration for bookloupe
     6.8 +
     6.9 +[options]
    6.10 +# Ignore DP-specific markup
    6.11 +dp=false
    6.12 +# Echo queried line
    6.13 +echo=true
    6.14 +# Check single quotes
    6.15 +squote=false
    6.16 +# Check common typos
    6.17 +typo=true
    6.18 +# Require closure of quotes on every paragraph
    6.19 +qpara=false
    6.20 +# Enable paranoid querying of everything
    6.21 +paranoid=true
    6.22 +# Enable line end checking
    6.23 +line-end=true
    6.24 +# Overview: just show counts
    6.25 +overview=false
    6.26 +# Output errors to stdout instead of stderr
    6.27 +stdout=false
    6.28 +# Echo header fields
    6.29 +header=false
    6.30 +# Ignore markup in < >
    6.31 +markup=false
    6.32 +# Use file of user-defined typos
    6.33 +usertypo=false
    6.34 +# Verbose - list everything
    6.35 +verbose=false
    6.36 +**************** EXPECTED(stdout) ****************
    6.37 +# Default configuration for bookloupe
    6.38 +
    6.39 +[options]
    6.40 +# Ignore DP-specific markup
    6.41 +dp=false
    6.42 +# Echo queried line
    6.43 +echo=true
    6.44 +# Check single quotes
    6.45 +squote=false
    6.46 +# Check common typos
    6.47 +typo=true
    6.48 +# Require closure of quotes on every paragraph
    6.49 +qpara=false
    6.50 +# Enable paranoid querying of everything
    6.51 +paranoid=true
    6.52 +# Enable line end checking
    6.53 +line-end=true
    6.54 +# Overview: just show counts
    6.55 +overview=false
    6.56 +# Output errors to stdout instead of stderr
    6.57 +stdout=false
    6.58 +# Echo header fields
    6.59 +header=false
    6.60 +# Ignore markup in < >
    6.61 +markup=false
    6.62 +# Use file of user-defined typos
    6.63 +usertypo=false
    6.64 +# Verbose - list everything
    6.65 +verbose=false
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/test/bookloupe/config-internal.tst	Fri Oct 25 11:15:18 2013 +0100
     7.3 @@ -0,0 +1,32 @@
     7.4 +**************** OPTIONS ****************
     7.5 +--dump-config
     7.6 +**************** EXPECTED(stdout) ****************
     7.7 +# Default configuration for bookloupe
     7.8 +
     7.9 +[options]
    7.10 +# Ignore DP-specific markup
    7.11 +dp=false
    7.12 +# Echo queried line
    7.13 +echo=true
    7.14 +# Check single quotes
    7.15 +squote=false
    7.16 +# Check common typos
    7.17 +typo=true
    7.18 +# Require closure of quotes on every paragraph
    7.19 +qpara=false
    7.20 +# Enable paranoid querying of everything
    7.21 +paranoid=true
    7.22 +# Enable line end checking
    7.23 +line-end=true
    7.24 +# Overview: just show counts
    7.25 +overview=false
    7.26 +# Output errors to stdout instead of stderr
    7.27 +stdout=false
    7.28 +# Echo header fields
    7.29 +header=false
    7.30 +# Ignore markup in < >
    7.31 +markup=false
    7.32 +# Use file of user-defined typos
    7.33 +usertypo=false
    7.34 +# Verbose - list everything
    7.35 +verbose=false
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/test/bookloupe/config-override.tst	Fri Oct 25 11:15:18 2013 +0100
     8.3 @@ -0,0 +1,63 @@
     8.4 +**************** OPTIONS ****************
     8.5 +--usertypo
     8.6 +--dump-config
     8.7 +**************** INPUT(bookloupe.ini) ****************
     8.8 +# Relaxed configuration for bookloupe
     8.9 +
    8.10 +[options]
    8.11 +# Ignore DP-specific markup
    8.12 +dp=false
    8.13 +# Echo queried line
    8.14 +echo=true
    8.15 +# Check single quotes
    8.16 +squote=false
    8.17 +# Check common typos
    8.18 +typo=true
    8.19 +# Require closure of quotes on every paragraph
    8.20 +qpara=false
    8.21 +# Enable paranoid querying of everything
    8.22 +paranoid=false
    8.23 +# Enable line end checking
    8.24 +line-end=true
    8.25 +# Overview: just show counts
    8.26 +overview=false
    8.27 +# Output errors to stdout instead of stderr
    8.28 +stdout=false
    8.29 +# Echo header fields
    8.30 +header=false
    8.31 +# Ignore markup in < >
    8.32 +markup=false
    8.33 +# Use file of user-defined typos
    8.34 +usertypo=false
    8.35 +# Verbose - list everything
    8.36 +verbose=false
    8.37 +**************** EXPECTED(stdout) ****************
    8.38 +# Relaxed configuration for bookloupe
    8.39 +
    8.40 +[options]
    8.41 +# Ignore DP-specific markup
    8.42 +dp=false
    8.43 +# Echo queried line
    8.44 +echo=true
    8.45 +# Check single quotes
    8.46 +squote=false
    8.47 +# Check common typos
    8.48 +typo=true
    8.49 +# Require closure of quotes on every paragraph
    8.50 +qpara=false
    8.51 +# Enable paranoid querying of everything
    8.52 +paranoid=false
    8.53 +# Enable line end checking
    8.54 +line-end=true
    8.55 +# Overview: just show counts
    8.56 +overview=false
    8.57 +# Output errors to stdout instead of stderr
    8.58 +stdout=false
    8.59 +# Echo header fields
    8.60 +header=false
    8.61 +# Ignore markup in < >
    8.62 +markup=false
    8.63 +# Use file of user-defined typos
    8.64 +usertypo=true
    8.65 +# Verbose - list everything
    8.66 +verbose=false
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/test/bookloupe/config-user.tst	Fri Oct 25 11:15:18 2013 +0100
     9.3 @@ -0,0 +1,72 @@
     9.4 +**************** OPTIONS ****************
     9.5 +--dump-config
     9.6 +**************** INPUT(bookloupe.ini) ****************
     9.7 +# Mary Contrary's configuration for bookloupe
     9.8 +
     9.9 +# Bookloupe will ignore this group, but it's nice to have.
    9.10 +[other]
    9.11 +# Look at me!
    9.12 +name="Mary Contrary"
    9.13 +
    9.14 +[options]
    9.15 +# Ignore DP-specific markup - sounds useful
    9.16 +dp=true
    9.17 +# Echo queried line - what's the point of that?
    9.18 +echo=false
    9.19 +# Check single quotes - yup
    9.20 +squote=true
    9.21 +# Check common typos - waste of time
    9.22 +typo=false
    9.23 +# Require closure of quotes on every paragraph - okay
    9.24 +qpara=true
    9.25 +# Enable paranoid querying of everything - Huh?
    9.26 +paranoid=false
    9.27 +# Enable line end checking - pointless
    9.28 +line-end=false
    9.29 +# Overview: just show counts - Brief is good
    9.30 +overview=true
    9.31 +# Output errors to stdout instead of stderr - keeps things together
    9.32 +stdout=true
    9.33 +# Echo header fields - I'd rather see it
    9.34 +header=true
    9.35 +# Ignore markup in < > - Need this
    9.36 +markup=true
    9.37 +# Use file of user-defined typos - And this
    9.38 +usertypo=true
    9.39 +# Verbose - list everything - Contrary by name...
    9.40 +verbose=true
    9.41 +**************** EXPECTED(stdout) ****************
    9.42 +# Mary Contrary's configuration for bookloupe
    9.43 +
    9.44 +# Bookloupe will ignore this group, but it's nice to have.
    9.45 +[other]
    9.46 +# Look at me!
    9.47 +name="Mary Contrary"
    9.48 +
    9.49 +[options]
    9.50 +# Ignore DP-specific markup - sounds useful
    9.51 +dp=true
    9.52 +# Echo queried line - what's the point of that?
    9.53 +echo=false
    9.54 +# Check single quotes - yup
    9.55 +squote=true
    9.56 +# Check common typos - waste of time
    9.57 +typo=false
    9.58 +# Require closure of quotes on every paragraph - okay
    9.59 +qpara=true
    9.60 +# Enable paranoid querying of everything - Huh?
    9.61 +paranoid=false
    9.62 +# Enable line end checking - pointless
    9.63 +line-end=false
    9.64 +# Overview: just show counts - Brief is good
    9.65 +overview=true
    9.66 +# Output errors to stdout instead of stderr - keeps things together
    9.67 +stdout=true
    9.68 +# Echo header fields - I'd rather see it
    9.69 +header=true
    9.70 +# Ignore markup in < > - Need this
    9.71 +markup=true
    9.72 +# Use file of user-defined typos - And this
    9.73 +usertypo=true
    9.74 +# Verbose - list everything - Contrary by name...
    9.75 +verbose=true
    10.1 --- a/test/compatibility/Makefile.am	Sat Oct 26 18:47:33 2013 +0100
    10.2 +++ b/test/compatibility/Makefile.am	Fri Oct 25 11:15:18 2013 +0100
    10.3 @@ -6,6 +6,7 @@
    10.4  	user-defined-typo.tst brackets.tst single-quotes.tst grave-quotes.tst \
    10.5  	dashes.tst control-characters.tst unusual-characters.tst \
    10.6  	windows-1252.tst periods.tst long-line.tst unmarked-paragraph.tst \
    10.7 +	paranoid.tst paranoid-typos.tst no-paranoid.tst no-paranoid-typos.tst \
    10.8  	hebe-jeebies.tst mail-from.tst scannos.tst before-comma.tst \
    10.9  	before-period.tst double-punctuation.tst genitives.tst embedded-cr.tst \
   10.10  	continuing-quotes.tst
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/test/compatibility/no-paranoid-typos.tst	Fri Oct 25 11:15:18 2013 +0100
    11.3 @@ -0,0 +1,12 @@
    11.4 +**************** OPTIONS ****************
    11.5 +-x
    11.6 +-t
    11.7 +**************** INPUT ****************
    11.8 +In paranoid mode we check for a standalone digits. 1 think this is a useful
    11.9 +feature. When checking for typos every, strangly placed comma is reported.
   11.10 +
   11.11 +If paranoid mode is switched off, we can still check for typos.
   11.12 +**************** EXPECTED ****************
   11.13 +
   11.14 +feature. When checking for typos every, strangly placed comma is reported.
   11.15 +    Line 2 column 39 - Query punctuation after every?
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/test/compatibility/no-paranoid.tst	Fri Oct 25 11:15:18 2013 +0100
    12.3 @@ -0,0 +1,8 @@
    12.4 +**************** OPTIONS ****************
    12.5 +-x
    12.6 +**************** INPUT ****************
    12.7 +In paranoid mode we check for a standalone digits. 1 think this is a useful
    12.8 +feature. When checking for typos every, strangly placed comma is reported.
    12.9 +
   12.10 +If paranoid mode is switched off, checking for typos defaults to off too.
   12.11 +**************** EXPECTED ****************
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/test/compatibility/paranoid-typos.tst	Fri Oct 25 11:15:18 2013 +0100
    13.3 @@ -0,0 +1,12 @@
    13.4 +**************** OPTIONS ****************
    13.5 +-t
    13.6 +**************** INPUT ****************
    13.7 +In paranoid mode we check for a standalone digits. 1 think this is a useful
    13.8 +feature. When checking for typos every, strangly placed comma is reported.
    13.9 +
   13.10 +In paranoid mode (the default), typo checking is switched off with its
   13.11 +short option.
   13.12 +**************** EXPECTED ****************
   13.13 +
   13.14 +In paranoid mode we check for a standalone digits. 1 think this is a useful
   13.15 +    Line 1 column 51 - Query standalone 1
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/test/compatibility/paranoid.tst	Fri Oct 25 11:15:18 2013 +0100
    14.3 @@ -0,0 +1,12 @@
    14.4 +**************** INPUT ****************
    14.5 +In paranoid mode we check for a standalone digits. 1 think this is a useful
    14.6 +feature. When checking for typos every, strangly placed comma is reported.
    14.7 +
    14.8 +By default, both paranoid mode and checking for typos should be on.
    14.9 +**************** EXPECTED ****************
   14.10 +
   14.11 +In paranoid mode we check for a standalone digits. 1 think this is a useful
   14.12 +    Line 1 column 51 - Query standalone 1
   14.13 +
   14.14 +feature. When checking for typos every, strangly placed comma is reported.
   14.15 +    Line 2 column 39 - Query punctuation after every?
    15.1 --- a/test/harness/Makefile.am	Sat Oct 26 18:47:33 2013 +0100
    15.2 +++ b/test/harness/Makefile.am	Fri Oct 25 11:15:18 2013 +0100
    15.3 @@ -5,5 +5,6 @@
    15.4  
    15.5  loupe_test_SOURCES=loupe-test.c testcase.c testcase.h testcaseio.c \
    15.6  	testcaseio.h testcaseparser.c testcaseparser.h testcaseinput.c \
    15.7 -	testcaseinput.h warningsparser.c warningsparser.h
    15.8 +	testcaseinput.h testcaseoutput.c testcaseoutput.h warningsparser.c \
    15.9 +	warningsparser.h
   15.10  loupe_test_LDADD=../../bl/libbl.la
    16.1 --- a/test/harness/loupe-test.c	Sat Oct 26 18:47:33 2013 +0100
    16.2 +++ b/test/harness/loupe-test.c	Fri Oct 25 11:15:18 2013 +0100
    16.3 @@ -48,6 +48,7 @@
    16.4  	exit(1);
    16.5      }
    16.6      bl_set_print_handlers();
    16.7 +    g_setenv("BOOKLOUPE_CONFIG_PATH",".",TRUE);
    16.8      for(i=1;i<argc;i++)
    16.9  	pass&=run_test(argv[i]);
   16.10      return pass?0:1;
    17.1 --- a/test/harness/testcase.c	Sat Oct 26 18:47:33 2013 +0100
    17.2 +++ b/test/harness/testcase.c	Fri Oct 25 11:15:18 2013 +0100
    17.3 @@ -7,6 +7,7 @@
    17.4  #include <bl/bl.h>
    17.5  #include "testcase.h"
    17.6  #include "testcaseinput.h"
    17.7 +#include "testcaseoutput.h"
    17.8  
    17.9  GQuark testcase_error_quark(void)
   17.10  {
   17.11 @@ -171,6 +172,64 @@
   17.12      return g_string_free(filename,FALSE);
   17.13  }
   17.14  
   17.15 +/*
   17.16 + * Verify that all the output files specified by a testcase are present
   17.17 + * with the expected contents. 
   17.18 + */
   17.19 +gboolean testcase_verify_output_files(Testcase *testcase)
   17.20 +{
   17.21 +    GSList *link;
   17.22 +    GError *tmp_err=NULL;
   17.23 +    gboolean retval=TRUE;
   17.24 +    ssize_t offset;
   17.25 +    gchar *contents;
   17.26 +    TestcaseOutput *output;
   17.27 +    for(link=testcase->outputs;link;link=link->next)
   17.28 +    {
   17.29 +	output=link->data;
   17.30 +	if (!testcase_output_read(testcase,output,&contents,NULL,&tmp_err))
   17.31 +	{
   17.32 +	    g_print("%s: FAIL\n",testcase->basename);
   17.33 +	    g_print("%s\n",tmp_err->message);
   17.34 +	    g_clear_error(&tmp_err);
   17.35 +	    retval=FALSE;
   17.36 +	    break;
   17.37 +	}
   17.38 +	else
   17.39 +	{
   17.40 +	    if (strcmp(contents,output->contents))
   17.41 +	    {
   17.42 +		g_print("%s: FAIL\n",testcase->basename);
   17.43 +		offset=common_prefix_length(contents,output->contents);
   17.44 +		if (!offset && !contents[offset])
   17.45 +		    g_print("%s: Unexpected empty output from bookloupe.\n",
   17.46 +		      output->name);
   17.47 +		else
   17.48 +		{
   17.49 +		    g_print("%s: Unexpected output from bookloupe:\n",
   17.50 +		      output->name);
   17.51 +		    print_unexpected(contents,offset);
   17.52 +		}
   17.53 +		retval=FALSE;
   17.54 +	    }
   17.55 +	    g_free(contents);
   17.56 +	    break;
   17.57 +	}
   17.58 +    }
   17.59 +    for(link=testcase->outputs;link;link=link->next)
   17.60 +	if (!testcase_output_remove(testcase,link->data,&tmp_err))
   17.61 +	{
   17.62 +	    if (retval)
   17.63 +	    {
   17.64 +		g_print("%s: FAIL\n",testcase->basename);
   17.65 +		g_print("%s\n",tmp_err->message);
   17.66 +		retval=TRUE;
   17.67 +	    }
   17.68 +	    g_clear_error(&tmp_err);
   17.69 +	}
   17.70 +    return retval;
   17.71 +}
   17.72 +
   17.73  gboolean testcase_spawn_bookloupe(Testcase *testcase,char **standard_output,
   17.74    GError **error)
   17.75  {
   17.76 @@ -496,7 +555,7 @@
   17.77      gboolean r;
   17.78      size_t pos,offset;
   17.79      GString *header;
   17.80 -    char *output,*filename,*s,*summary,*xfail=NULL;
   17.81 +    char *filename,*s,*summary,*xfail=NULL;
   17.82      GError *error=NULL;
   17.83      if (!testcase_create_input_files(testcase,&error))
   17.84      {
   17.85 @@ -505,7 +564,7 @@
   17.86  	g_error_free(error);
   17.87  	return FALSE;
   17.88      }
   17.89 -    r=testcase_spawn_bookloupe(testcase,&output,&error);
   17.90 +    r=testcase_spawn_bookloupe(testcase,&testcase->test_output,&error);
   17.91      if (!r)
   17.92      {
   17.93  	g_print("%s: FAIL\n",testcase->basename);
   17.94 @@ -522,41 +581,47 @@
   17.95  	g_error_free(error);
   17.96  	return FALSE;
   17.97      }
   17.98 -    header=g_string_new("\n\nFile: ");
   17.99 -    g_string_append(header,filename);
  17.100 -    g_string_append(header,"\n");
  17.101 -    if (!g_str_has_prefix(output,header->str))
  17.102 +    if (testcase->expected || testcase->warnings)
  17.103      {
  17.104 -	g_print("%s: FAIL\n",testcase->basename);
  17.105 -	g_print("Unexpected header from bookloupe:\n");
  17.106 -	offset=common_prefix_length(output,header->str);
  17.107 -	print_unexpected(output,offset);
  17.108 -	r=FALSE;
  17.109 -    }
  17.110 -    pos=header->len;
  17.111 -    if (r)
  17.112 -    {
  17.113 -	/* Find the end of the summary */
  17.114 -	s=strstr(output+pos,"\n\n");
  17.115 -	if (s)
  17.116 -	{
  17.117 -	    summary=g_strndup(output+pos,s-(output+pos));
  17.118 -	    r=testcase_check_summary(testcase,summary);
  17.119 -	    g_free(summary);
  17.120 -	    pos=s-output+2;
  17.121 -	}
  17.122 -	else
  17.123 +	header=g_string_new("\n\nFile: ");
  17.124 +	g_string_append(header,filename);
  17.125 +	g_string_append(header,"\n");
  17.126 +	if (!g_str_has_prefix(testcase->test_output,header->str))
  17.127  	{
  17.128  	    g_print("%s: FAIL\n",testcase->basename);
  17.129 -	    g_print("Unterminated summary from bookloupe:\n%s\n",output+pos);
  17.130 +	    g_print("Unexpected header from bookloupe:\n");
  17.131 +	    offset=common_prefix_length(testcase->test_output,header->str);
  17.132 +	    print_unexpected(testcase->test_output,offset);
  17.133  	    r=FALSE;
  17.134  	}
  17.135 +	summary=testcase->test_output+header->len;
  17.136 +	pos=header->len;
  17.137 +	if (r)
  17.138 +	{
  17.139 +	    /* Find the end of the summary */
  17.140 +	    s=strstr(summary,"\n\n");
  17.141 +	    if (s)
  17.142 +	    {
  17.143 +		summary=g_strndup(summary,s-summary);
  17.144 +		r=testcase_check_summary(testcase,summary);
  17.145 +		g_free(summary);
  17.146 +		pos=s-testcase->test_output+2;
  17.147 +	    }
  17.148 +	    else
  17.149 +	    {
  17.150 +		g_print("%s: FAIL\n",testcase->basename);
  17.151 +		g_print("Unterminated summary from bookloupe:\n%s\n",summary);
  17.152 +		r=FALSE;
  17.153 +	    }
  17.154 +	}
  17.155 +	g_string_free(header,TRUE);
  17.156 +	if (r)
  17.157 +	    r=testcase_check_warnings(testcase,testcase->test_output+pos,
  17.158 +	      &xfail);
  17.159      }
  17.160 -    g_string_free(header,TRUE);
  17.161 -    if (r)
  17.162 -	r=testcase_check_warnings(testcase,output+pos,&xfail);
  17.163 +    if (!testcase_verify_output_files(testcase))
  17.164 +	r=FALSE;
  17.165      g_free(filename);
  17.166 -    g_free(output);
  17.167      if (r)
  17.168      {
  17.169  	if (xfail)
  17.170 @@ -617,5 +682,6 @@
  17.171      g_slist_free(testcase->warnings);
  17.172      g_free(testcase->encoding);
  17.173      g_strfreev(testcase->options);
  17.174 +    g_free(testcase->test_output);
  17.175      g_free(testcase);
  17.176  }
    18.1 --- a/test/harness/testcase.h	Sat Oct 26 18:47:33 2013 +0100
    18.2 +++ b/test/harness/testcase.h	Fri Oct 25 11:15:18 2013 +0100
    18.3 @@ -41,11 +41,13 @@
    18.4      char *basename;
    18.5      char *tmpdir;
    18.6      GSList *inputs;
    18.7 +    GSList *outputs;
    18.8      char *expected;
    18.9      TestcaseSummary summary;
   18.10      GSList *warnings;
   18.11      char *encoding;	/* The character encoding to talk to BOOKLOUPE in */
   18.12      char **options;
   18.13 +    char *test_output;
   18.14      enum {
   18.15  	TESTCASE_XFAIL=1<<0,
   18.16  	TESTCASE_TMP_DIR=1<<1,
    19.1 --- a/test/harness/testcaseio.c	Sat Oct 26 18:47:33 2013 +0100
    19.2 +++ b/test/harness/testcaseio.c	Fri Oct 25 11:15:18 2013 +0100
    19.3 @@ -5,6 +5,7 @@
    19.4  #include <bl/bl.h>
    19.5  #include "testcaseparser.h"
    19.6  #include "testcaseinput.h"
    19.7 +#include "testcaseoutput.h"
    19.8  #include "testcaseio.h"
    19.9  #include "warningsparser.h"
   19.10  
   19.11 @@ -70,6 +71,25 @@
   19.12  	else if (!testcase->expected && !testcase->warnings &&
   19.13  	  !strcmp(tag,"EXPECTED"))
   19.14  	    testcase->expected=g_strdup(text);
   19.15 +	else if (g_str_has_prefix(tag,"EXPECTED(") && tag[strlen(tag)-1]==')')
   19.16 +	{
   19.17 +	    arg=g_strndup(tag+9,strlen(tag)-10);
   19.18 +	    s=g_path_get_dirname(arg);
   19.19 +	    if (strcmp(s,"."))
   19.20 +	    {
   19.21 +		g_printerr("%s: Expected files may not have a "
   19.22 +		  "directory component\n",arg);
   19.23 +		g_free(s);
   19.24 +		g_free(arg);
   19.25 +		testcase_free(testcase);
   19.26 +		testcase_parser_free(parser);
   19.27 +		return NULL;
   19.28 +	    }
   19.29 +	    g_free(s);
   19.30 +	    testcase->outputs=g_slist_prepend(testcase->outputs,
   19.31 +	      testcase_output_new(arg,text));
   19.32 +	    g_free(arg);
   19.33 +	}
   19.34  	else if (!testcase->expected && !testcase->warnings &&
   19.35  	  !strcmp(tag,"WARNINGS"))
   19.36  	{
   19.37 @@ -108,11 +128,14 @@
   19.38  	    }
   19.39  	    g_free(s);
   19.40  	}
   19.41 -	else if (!testcase->encoding && !strcmp(tag,"OPTIONS"))
   19.42 +	else if (!testcase->options && !strcmp(tag,"OPTIONS"))
   19.43  	{
   19.44  	    testcase->options=g_strsplit(text,"\n",0);
   19.45 -	    g_free(testcase->options[g_strv_length(testcase->options)-1]);
   19.46 -	    testcase->options[g_strv_length(testcase->options)-1]=NULL;
   19.47 +	    if (testcase->options && g_strv_length(testcase->options)>0)
   19.48 +	    {
   19.49 +		g_free(testcase->options[g_strv_length(testcase->options)-1]);
   19.50 +		testcase->options[g_strv_length(testcase->options)-1]=NULL;
   19.51 +	    }
   19.52  	}
   19.53  	else
   19.54  	{
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/test/harness/testcaseoutput.c	Fri Oct 25 11:15:18 2013 +0100
    20.3 @@ -0,0 +1,140 @@
    20.4 +#include <stdlib.h>
    20.5 +#include <string.h>
    20.6 +#include <errno.h>
    20.7 +#include <glib.h>
    20.8 +#include <bl/bl.h>
    20.9 +#include "testcase.h"
   20.10 +#include "testcaseoutput.h"
   20.11 +
   20.12 +/*
   20.13 + * Replace \r\n with \n, \n with U+240A (visible symbol for LF)
   20.14 + * and \r with U+240D (visible symbol for CR).
   20.15 + */
   20.16 +static char *dos2unix(const char *text)
   20.17 +{
   20.18 +    gunichar c;
   20.19 +    gboolean cr=FALSE;
   20.20 +    const gunichar visible_lf=0x240A;
   20.21 +    const gunichar visible_cr=0x240D;
   20.22 +    GString *string;
   20.23 +    string=g_string_new(NULL);
   20.24 +    while(*text)
   20.25 +    {
   20.26 +	c=g_utf8_get_char(text);
   20.27 +	text=g_utf8_next_char(text);
   20.28 +	if (cr)
   20.29 +	{
   20.30 +	    cr=FALSE;
   20.31 +	    if (c=='\n')
   20.32 +	    {
   20.33 +		g_string_append_c(string,'\n');
   20.34 +		continue;
   20.35 +	    }
   20.36 +	    else
   20.37 +		g_string_append_unichar(string,visible_cr);
   20.38 +	}
   20.39 +	if (c=='\r')
   20.40 +	    cr=TRUE;
   20.41 +	else if (c=='\n')
   20.42 +	    g_string_append_unichar(string,visible_lf);
   20.43 +	else
   20.44 +	    g_string_append_unichar(string,c);
   20.45 +    }
   20.46 +    if (cr)
   20.47 +	g_string_append_unichar(string,visible_cr);
   20.48 +    return g_string_free(string,FALSE);
   20.49 +}
   20.50 +
   20.51 +/*
   20.52 + * Read an output file needed for a testcase (as specified in <output>).
   20.53 + * The file is read in the encoding specified for communicating with
   20.54 + * bookloupe.
   20.55 + */
   20.56 +gboolean testcase_output_read(Testcase *testcase,TestcaseOutput *output,
   20.57 +  gchar **contents,gsize *length,GError **error)
   20.58 +{
   20.59 +    char *filename,*s,*t;
   20.60 +    gboolean retval;
   20.61 +    GError *tmp_err=NULL;
   20.62 +    if (!strcmp(output->name,"stdout"))
   20.63 +    {
   20.64 +	*contents=g_strdup(testcase->test_output);
   20.65 +	if (length)
   20.66 +	    *length=strlen(testcase->test_output);
   20.67 +    }
   20.68 +    else
   20.69 +    {
   20.70 +	if (testcase->tmpdir)
   20.71 +	    filename=g_build_filename(testcase->tmpdir,output->name,NULL);
   20.72 +	else
   20.73 +	    filename=g_strdup(output->name);
   20.74 +	if (!g_file_get_contents(filename,&s,NULL,error))
   20.75 +	{
   20.76 +	    g_free(filename);
   20.77 +	    return FALSE;
   20.78 +	}
   20.79 +	g_free(filename);
   20.80 +	if (testcase->encoding)
   20.81 +	{
   20.82 +	    t=dos2unix(s);
   20.83 +	    g_free(s);
   20.84 +	    s=g_convert(t,-1,"UTF-8",testcase->encoding,NULL,length,&tmp_err);
   20.85 +	    g_free(t);
   20.86 +	    if (!s)
   20.87 +	    {
   20.88 +		g_propagate_prefixed_error(error,tmp_err,
   20.89 +		  "Conversion from %s failed: ",testcase->encoding);
   20.90 +		return FALSE;
   20.91 +	    }
   20.92 +	    *contents=s;
   20.93 +	}
   20.94 +	else
   20.95 +	{
   20.96 +	    *contents=dos2unix(s);
   20.97 +	    if (length)
   20.98 +		*length=strlen(*contents);
   20.99 +	}
  20.100 +    }
  20.101 +    return TRUE;
  20.102 +}
  20.103 +
  20.104 +/*
  20.105 + * Remove an output file created by program under test.
  20.106 + */
  20.107 +gboolean testcase_output_remove(Testcase *testcase,TestcaseOutput *output,
  20.108 +  GError **error)
  20.109 +{
  20.110 +    char *filename;
  20.111 +    if (!strcmp(output->name,"stdout"))
  20.112 +	return TRUE;
  20.113 +    if (testcase->tmpdir)
  20.114 +	filename=g_build_filename(testcase->tmpdir,output->name,NULL);
  20.115 +    else
  20.116 +	filename=g_strdup(output->name);
  20.117 +    if (g_unlink(filename)<0)
  20.118 +    {
  20.119 +	g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
  20.120 +	  "%s: %s",filename,g_strerror(errno));
  20.121 +	return FALSE;
  20.122 +    }
  20.123 +    g_free(filename);
  20.124 +    return TRUE;
  20.125 +}
  20.126 +
  20.127 +/* Create a new description of an output file expected by a testcase */
  20.128 +TestcaseOutput *testcase_output_new(const char *name,const char *contents)
  20.129 +{
  20.130 +    TestcaseOutput *output;
  20.131 +    output=g_new0(TestcaseOutput,1);
  20.132 +    output->name=g_strdup(name);
  20.133 +    output->contents=g_strdup(contents);
  20.134 +    return output;
  20.135 +}
  20.136 +
  20.137 +/* Free the description of a testcase output file */
  20.138 +void testcase_output_free(TestcaseOutput *output)
  20.139 +{
  20.140 +    g_free(output->name);
  20.141 +    g_free(output->contents);
  20.142 +    g_free(output);
  20.143 +}
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/test/harness/testcaseoutput.h	Fri Oct 25 11:15:18 2013 +0100
    21.3 @@ -0,0 +1,19 @@
    21.4 +#ifndef TESTCASE_OUTPUT_H
    21.5 +#define TESTCASE_OUTPUT_H
    21.6 +
    21.7 +#include <glib.h>
    21.8 +#include "testcase.h"
    21.9 +
   21.10 +typedef struct {
   21.11 +    char *name;
   21.12 +    char *contents;
   21.13 +} TestcaseOutput;
   21.14 +
   21.15 +gboolean testcase_output_read(Testcase *testcase,TestcaseOutput *output,
   21.16 +  gchar **contents,gsize *length,GError **error);
   21.17 +gboolean testcase_output_remove(Testcase *testcase,TestcaseOutput *output,
   21.18 +  GError **error);
   21.19 +TestcaseOutput *testcase_output_new(const char *name,const char *contents);
   21.20 +void testcase_output_free(TestcaseOutput *output);
   21.21 +
   21.22 +#endif	/* TESTCASE_OUTPUT_H */