# HG changeset patch # User ali # Date 1382696118 -3600 # Node ID ff0aa9b1397accc693bba14931b32178d871bcdb # Parent f44c530f80da57947086d347e820a285e62bce92 Fix bug #14: Add a configuration file diff -r f44c530f80da -r ff0aa9b1397a Makefile.am --- a/Makefile.am Sat Oct 26 18:47:33 2013 +0100 +++ b/Makefile.am Fri Oct 25 11:15:18 2013 +0100 @@ -1,1 +1,3 @@ SUBDIRS=bl bookloupe test doc + +dist_pkgdata_DATA=sample.ini diff -r f44c530f80da -r ff0aa9b1397a bookloupe/bookloupe.c --- a/bookloupe/bookloupe.c Sat Oct 26 18:47:33 2013 +0100 +++ b/bookloupe/bookloupe.c Fri Oct 25 11:15:18 2013 +0100 @@ -128,35 +128,97 @@ gboolean pswit[SWITNO]; /* program switches */ +gboolean typo_compat,paranoid_compat; + static GOptionEntry options[]={ { "dp", 'd', 0, G_OPTION_ARG_NONE, pswit+DP_SWITCH, "Ignore DP-specific markup", NULL }, - { "noecho", 'e', 0, G_OPTION_ARG_NONE, pswit+ECHO_SWITCH, + { "no-dp", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+DP_SWITCH, + "Don't ignore DP-specific markup", NULL }, + { "echo", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, pswit+ECHO_SWITCH, + "Echo queried line", NULL }, + { "no-echo", 'e', G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+ECHO_SWITCH, "Don't echo queried line", NULL }, { "squote", 's', 0, G_OPTION_ARG_NONE, pswit+SQUOTE_SWITCH, "Check single quotes", NULL }, - { "typo", 't', 0, G_OPTION_ARG_NONE, pswit+TYPO_SWITCH, + { "no-squote", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+SQUOTE_SWITCH, + "Don't check single quotes", NULL }, + { "typo", 0, 0, G_OPTION_ARG_NONE, pswit+TYPO_SWITCH, "Check common typos", NULL }, + { "no-typo", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+TYPO_SWITCH, + "Don't check common typos", NULL }, { "qpara", 'p', 0, G_OPTION_ARG_NONE, pswit+QPARA_SWITCH, "Require closure of quotes on every paragraph", NULL }, - { "relaxed", 'x', 0, G_OPTION_ARG_NONE, pswit+PARANOID_SWITCH, + { "no-qpara", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+QPARA_SWITCH, + "Don't require closure of quotes on every paragraph", NULL }, + { "paranoid", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_NONE, pswit+PARANOID_SWITCH, + "Enable paranoid querying of everything", NULL }, + { "no-paranoid", 0, G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+PARANOID_SWITCH, "Disable paranoid querying of everything", NULL }, - { "line-end", 'l', 0, G_OPTION_ARG_NONE, pswit+LINE_END_SWITCH, + { "line-end", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_NONE, pswit+LINE_END_SWITCH, + "Enable line end checking", NULL }, + { "no-line-end", 'l', G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+LINE_END_SWITCH, "Disable line end checking", NULL }, { "overview", 'o', 0, G_OPTION_ARG_NONE, pswit+OVERVIEW_SWITCH, "Overview: just show counts", NULL }, + { "no-overview", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+OVERVIEW_SWITCH, + "Show individual warnings", NULL }, { "stdout", 'y', 0, G_OPTION_ARG_NONE, pswit+STDOUT_SWITCH, "Output errors to stdout instead of stderr", NULL }, + { "no-stdout", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+STDOUT_SWITCH, + "Output errors to stderr instead of stdout", NULL }, { "header", 'h', 0, G_OPTION_ARG_NONE, pswit+HEADER_SWITCH, "Echo header fields", NULL }, + { "no-header", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+HEADER_SWITCH, + "Don't echo header fields", NULL }, { "markup", 'm', 0, G_OPTION_ARG_NONE, pswit+MARKUP_SWITCH, "Ignore markup in < >", NULL }, + { "no-markup", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+MARKUP_SWITCH, + "No special handling for markup in < >", NULL }, { "usertypo", 'u', 0, G_OPTION_ARG_NONE, pswit+USERTYPO_SWITCH, "Use file of user-defined typos", NULL }, + { "no-usertypo", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+USERTYPO_SWITCH, + "Ignore file of user-defined typos", NULL }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, pswit+VERBOSE_SWITCH, + "Verbose - list everything", NULL }, + { "no-verbose", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, pswit+VERBOSE_SWITCH, + "Switch off verbose mode", NULL }, + { NULL } +}; + +/* + * Options relating to configuration which make no sense from inside + * a configuration file. + */ + +static GOptionEntry config_options[]={ { "web", 'w', 0, G_OPTION_ARG_NONE, pswit+WEB_SWITCH, "Defaults for use on www upload", NULL }, - { "verbose", 'v', 0, G_OPTION_ARG_NONE, pswit+VERBOSE_SWITCH, - "Verbose - list everything", NULL }, + { "dump-config", 0, 0, G_OPTION_ARG_NONE, pswit+DUMP_CONFIG_SWITCH, + "Dump current config settings", NULL }, + { NULL } +}; + +static GOptionEntry compatibility_options[]={ + { "toggle-typo", 't', 0, G_OPTION_ARG_NONE, &typo_compat, + "Toggle checking for common typos", NULL }, + { "toggle-relaxed", 'x', 0, G_OPTION_ARG_NONE, ¶noid_compat, + "Toggle both paranoid mode and common typos", NULL }, { NULL } }; @@ -200,31 +262,198 @@ UINT saved_cp; #endif +GKeyFile *config; + +void config_file_update(GKeyFile *kf) +{ + int i; + gboolean sw; + for(i=0;options[i].long_name;i++) + { + if (g_str_has_prefix(options[i].long_name,"no-")) + continue; + if (options[i].arg==G_OPTION_ARG_NONE) + { + sw=*(gboolean *)options[i].arg_data; + if (options[i].flags&G_OPTION_FLAG_REVERSE) + sw=!sw; + g_key_file_set_boolean(kf,"options",options[i].long_name,sw); + } + else + g_assert_not_reached(); + } +} + +void config_file_add_comments(GKeyFile *kf) +{ + int i; + gchar *comment; + g_key_file_set_comment(kf,NULL,NULL," Default configuration for bookloupe", + NULL); + for(i=0;options[i].long_name;i++) + { + if (g_str_has_prefix(options[i].long_name,"no-")) + continue; + comment=g_strconcat(" ",options[i].description,NULL); + g_key_file_set_comment(kf,"options",options[i].long_name,comment,NULL); + g_free(comment); + } +} + +void dump_config(void) +{ + gchar *s; + if (config) + config_file_update(config); + else + { + config=g_key_file_new(); + config_file_update(config); + config_file_add_comments(config); + } + s=g_key_file_to_data(config,NULL,NULL); + if (s) + g_print("%s",s); + g_free(s); +} + +GKeyFile *read_config_file(gchar **full_path) +{ + int i; + GError *err=NULL; + gchar **search_dirs; + gchar *path; + const char *search_path; + GKeyFile *kf; + kf=g_key_file_new(); + search_path=g_getenv("BOOKLOUPE_CONFIG_PATH"); + if (search_path) + { +#ifdef __WIN32__ + search_dirs=g_strsplit(search_path,";",0); +#else + search_dirs=g_strsplit(search_path,":",0); +#endif + } + else + { + search_dirs=g_new(gchar *,4); + search_dirs[0]=g_get_current_dir(); + search_dirs[1]=g_strdup(running_from); + search_dirs[2]=g_strdup(g_get_user_config_dir()); + search_dirs[3]=NULL; + } + for(i=0;search_dirs[i];i++) + { + path=g_build_filename(search_dirs[i],"bookloupe.ini",NULL); + if (g_key_file_load_from_file(kf,path, + G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS,&err)) + break; + if (!g_error_matches(err,G_FILE_ERROR,G_FILE_ERROR_NOENT)) + { + g_printerr("Bookloupe: Error reading %s\n",path); + g_printerr("%s\n",err->message); + exit(1); + } + g_clear_error(&err); + g_free(path); + path=NULL; + } + if (!search_dirs[i]) + { + g_key_file_free(kf); + kf=NULL; + } + g_strfreev(search_dirs); + if (full_path && kf) + *full_path=path; + else + g_free(path); + return kf; +} + +void parse_config_file(void) +{ + int i,j; + gchar *path; + gchar **keys; + gboolean sw; + GError *err=NULL; + config=read_config_file(&path); + if (config) + keys=g_key_file_get_keys(config,"options",NULL,NULL); + else + keys=NULL; + if (keys) + { + for(i=0;keys[i];i++) + { + for(j=0;options[j].long_name;j++) + { + if (g_str_has_prefix(options[j].long_name,"no-")) + continue; + else if (!strcmp(keys[i],options[j].long_name)) + { + if (options[j].arg==G_OPTION_ARG_NONE) + { + sw=g_key_file_get_boolean(config,"options",keys[i], + &err); + if (err) + { + g_printerr("Bookloupe: %s: options.%s: %s\n", + path,keys[i],err->message); + g_clear_error(&err); + } + if (options[j].flags&G_OPTION_FLAG_REVERSE) + sw=!sw; + *(gboolean *)options[j].arg_data=sw; + break; + } + else + g_assert_not_reached(); + } + } + if (!options[j].long_name) + g_printerr("Bookloupe: %s: Unknown option \"%s\" ignored\n", + path,keys[i]); + } + g_strfreev(keys); + } + if (config) + g_free(path); +} + void parse_options(int *argc,char ***argv) { GError *err=NULL; GOptionContext *context; + GOptionGroup *compatibility; context=g_option_context_new( - "file - looks for errors in Project Gutenberg(TM) etexts"); + "file - look for errors in Project Gutenberg(TM) etexts"); g_option_context_add_main_entries(context,options,NULL); + g_option_context_add_main_entries(context,config_options,NULL); + compatibility=g_option_group_new("compatibility", + "Options for Compatibility with Gutcheck:", + "Show compatibility options",NULL,NULL); + g_option_group_add_entries(compatibility,compatibility_options); + g_option_context_add_group(context,compatibility); + g_option_context_set_description(context, + "For simplicity, only the switch options which reverse the\n" + "default configuration are listed. In most cases, both vanilla\n" + "and \"no-\" prefixed versions are available for use."); if (!g_option_context_parse(context,argc,argv,&err)) { g_printerr("Bookloupe: %s\n",err->message); g_printerr("Use \"%s --help\" for help\n",(*argv)[0]); exit(1); } - /* Paranoid checking is turned OFF, not on, by its switch */ - pswit[PARANOID_SWITCH]=!pswit[PARANOID_SWITCH]; - if (pswit[PARANOID_SWITCH]) - /* if running in paranoid mode, typo checks default to enabled */ + if (typo_compat) pswit[TYPO_SWITCH]=!pswit[TYPO_SWITCH]; - /* Line-end checking is turned OFF, not on, by its switch */ - pswit[LINE_END_SWITCH]=!pswit[LINE_END_SWITCH]; - /* Echoing is turned OFF, not on, by its switch */ - pswit[ECHO_SWITCH]=!pswit[ECHO_SWITCH]; - if (pswit[OVERVIEW_SWITCH]) - /* just print summary; don't echo */ - pswit[ECHO_SWITCH]=FALSE; + if (paranoid_compat) + { + pswit[PARANOID_SWITCH]=!pswit[PARANOID_SWITCH]; + pswit[TYPO_SWITCH]=!pswit[TYPO_SWITCH]; + } /* * Web uploads - for the moment, this is really just a placeholder * until we decide what processing we really want to do on web uploads @@ -246,6 +475,14 @@ pswit[USERTYPO_SWITCH]=FALSE; pswit[DP_SWITCH]=FALSE; } + if (pswit[DUMP_CONFIG_SWITCH]) + { + dump_config(); + exit(0); + } + if (pswit[OVERVIEW_SWITCH]) + /* just print summary; don't echo */ + pswit[ECHO_SWITCH]=FALSE; if (*argc<2) { proghelp(context); @@ -388,6 +625,15 @@ saved_cp=GetConsoleOutputCP(); #endif running_from=g_path_get_dirname(argv[0]); + /* Paranoid checking is turned OFF, not on, by its switch */ + pswit[PARANOID_SWITCH]=TRUE; + /* if running in paranoid mode, typo checks default to enabled */ + pswit[TYPO_SWITCH]=TRUE; + /* Line-end checking is turned OFF, not on, by its switch */ + pswit[LINE_END_SWITCH]=TRUE; + /* Echoing is turned OFF, not on, by its switch */ + pswit[ECHO_SWITCH]=TRUE; + parse_config_file(); parse_options(&argc,&argv); if (pswit[USERTYPO_SWITCH]) read_user_scannos(); @@ -428,6 +674,8 @@ g_free(running_from); if (usertypo) g_tree_unref(usertypo); + if (config) + g_key_file_free(config); return 0; } diff -r f44c530f80da -r ff0aa9b1397a bookloupe/bookloupe.h --- a/bookloupe/bookloupe.h Sat Oct 26 18:47:33 2013 +0100 +++ b/bookloupe/bookloupe.h Fri Oct 25 11:15:18 2013 +0100 @@ -55,6 +55,7 @@ MARKUP_SWITCH, USERTYPO_SWITCH, DP_SWITCH, + DUMP_CONFIG_SWITCH, SWITNO }; diff -r f44c530f80da -r ff0aa9b1397a sample.ini --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sample.ini Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,31 @@ +# Default configuration for bookloupe + +[options] +# Ignore DP-specific markup +dp=false +# Echo queried line +echo=true +# Check single quotes +squote=false +# Check common typos +typo=true +# Require closure of quotes on every paragraph +qpara=false +# Enable paranoid querying of everything +paranoid=true +# Enable line end checking +line-end=true +# Overview: just show counts +overview=false +# Output errors to stdout instead of stderr +stdout=false +# Echo header fields +header=false +# Ignore markup in < > +markup=false +# Use file of user-defined typos +usertypo=false +# Defaults for use on www upload +web=false +# Verbose - list everything +verbose=false diff -r f44c530f80da -r ff0aa9b1397a test/bookloupe/Makefile.am --- a/test/bookloupe/Makefile.am Sat Oct 26 18:47:33 2013 +0100 +++ b/test/bookloupe/Makefile.am Fri Oct 25 11:15:18 2013 +0100 @@ -1,6 +1,8 @@ TESTS_ENVIRONMENT=BOOKLOUPE=../../bookloupe/bookloupe ../harness/loupe-test TESTS=non-ascii.tst long-line.tst curved-single-quotes.tst curved-quotes.tst \ runfox-quotes.tst curved-genitives.tst multi-line-illustration.tst \ - emdash.tst footnote-marker.tst unix-lineends.tst os9-lineends.tst + emdash.tst config-internal.tst config-default.tst config-user.tst \ + config-override.tst footnote-marker.tst unix-lineends.tst \ + os9-lineends.tst dist_pkgdata_DATA=$(TESTS) diff -r f44c530f80da -r ff0aa9b1397a test/bookloupe/config-default.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/bookloupe/config-default.tst Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,62 @@ +**************** OPTIONS **************** +--dump-config +**************** INPUT(bookloupe.ini) **************** +# Default configuration for bookloupe + +[options] +# Ignore DP-specific markup +dp=false +# Echo queried line +echo=true +# Check single quotes +squote=false +# Check common typos +typo=true +# Require closure of quotes on every paragraph +qpara=false +# Enable paranoid querying of everything +paranoid=true +# Enable line end checking +line-end=true +# Overview: just show counts +overview=false +# Output errors to stdout instead of stderr +stdout=false +# Echo header fields +header=false +# Ignore markup in < > +markup=false +# Use file of user-defined typos +usertypo=false +# Verbose - list everything +verbose=false +**************** EXPECTED(stdout) **************** +# Default configuration for bookloupe + +[options] +# Ignore DP-specific markup +dp=false +# Echo queried line +echo=true +# Check single quotes +squote=false +# Check common typos +typo=true +# Require closure of quotes on every paragraph +qpara=false +# Enable paranoid querying of everything +paranoid=true +# Enable line end checking +line-end=true +# Overview: just show counts +overview=false +# Output errors to stdout instead of stderr +stdout=false +# Echo header fields +header=false +# Ignore markup in < > +markup=false +# Use file of user-defined typos +usertypo=false +# Verbose - list everything +verbose=false diff -r f44c530f80da -r ff0aa9b1397a test/bookloupe/config-internal.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/bookloupe/config-internal.tst Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,32 @@ +**************** OPTIONS **************** +--dump-config +**************** EXPECTED(stdout) **************** +# Default configuration for bookloupe + +[options] +# Ignore DP-specific markup +dp=false +# Echo queried line +echo=true +# Check single quotes +squote=false +# Check common typos +typo=true +# Require closure of quotes on every paragraph +qpara=false +# Enable paranoid querying of everything +paranoid=true +# Enable line end checking +line-end=true +# Overview: just show counts +overview=false +# Output errors to stdout instead of stderr +stdout=false +# Echo header fields +header=false +# Ignore markup in < > +markup=false +# Use file of user-defined typos +usertypo=false +# Verbose - list everything +verbose=false diff -r f44c530f80da -r ff0aa9b1397a test/bookloupe/config-override.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/bookloupe/config-override.tst Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,63 @@ +**************** OPTIONS **************** +--usertypo +--dump-config +**************** INPUT(bookloupe.ini) **************** +# Relaxed configuration for bookloupe + +[options] +# Ignore DP-specific markup +dp=false +# Echo queried line +echo=true +# Check single quotes +squote=false +# Check common typos +typo=true +# Require closure of quotes on every paragraph +qpara=false +# Enable paranoid querying of everything +paranoid=false +# Enable line end checking +line-end=true +# Overview: just show counts +overview=false +# Output errors to stdout instead of stderr +stdout=false +# Echo header fields +header=false +# Ignore markup in < > +markup=false +# Use file of user-defined typos +usertypo=false +# Verbose - list everything +verbose=false +**************** EXPECTED(stdout) **************** +# Relaxed configuration for bookloupe + +[options] +# Ignore DP-specific markup +dp=false +# Echo queried line +echo=true +# Check single quotes +squote=false +# Check common typos +typo=true +# Require closure of quotes on every paragraph +qpara=false +# Enable paranoid querying of everything +paranoid=false +# Enable line end checking +line-end=true +# Overview: just show counts +overview=false +# Output errors to stdout instead of stderr +stdout=false +# Echo header fields +header=false +# Ignore markup in < > +markup=false +# Use file of user-defined typos +usertypo=true +# Verbose - list everything +verbose=false diff -r f44c530f80da -r ff0aa9b1397a test/bookloupe/config-user.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/bookloupe/config-user.tst Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,72 @@ +**************** OPTIONS **************** +--dump-config +**************** INPUT(bookloupe.ini) **************** +# Mary Contrary's configuration for bookloupe + +# Bookloupe will ignore this group, but it's nice to have. +[other] +# Look at me! +name="Mary Contrary" + +[options] +# Ignore DP-specific markup - sounds useful +dp=true +# Echo queried line - what's the point of that? +echo=false +# Check single quotes - yup +squote=true +# Check common typos - waste of time +typo=false +# Require closure of quotes on every paragraph - okay +qpara=true +# Enable paranoid querying of everything - Huh? +paranoid=false +# Enable line end checking - pointless +line-end=false +# Overview: just show counts - Brief is good +overview=true +# Output errors to stdout instead of stderr - keeps things together +stdout=true +# Echo header fields - I'd rather see it +header=true +# Ignore markup in < > - Need this +markup=true +# Use file of user-defined typos - And this +usertypo=true +# Verbose - list everything - Contrary by name... +verbose=true +**************** EXPECTED(stdout) **************** +# Mary Contrary's configuration for bookloupe + +# Bookloupe will ignore this group, but it's nice to have. +[other] +# Look at me! +name="Mary Contrary" + +[options] +# Ignore DP-specific markup - sounds useful +dp=true +# Echo queried line - what's the point of that? +echo=false +# Check single quotes - yup +squote=true +# Check common typos - waste of time +typo=false +# Require closure of quotes on every paragraph - okay +qpara=true +# Enable paranoid querying of everything - Huh? +paranoid=false +# Enable line end checking - pointless +line-end=false +# Overview: just show counts - Brief is good +overview=true +# Output errors to stdout instead of stderr - keeps things together +stdout=true +# Echo header fields - I'd rather see it +header=true +# Ignore markup in < > - Need this +markup=true +# Use file of user-defined typos - And this +usertypo=true +# Verbose - list everything - Contrary by name... +verbose=true diff -r f44c530f80da -r ff0aa9b1397a test/compatibility/Makefile.am --- a/test/compatibility/Makefile.am Sat Oct 26 18:47:33 2013 +0100 +++ b/test/compatibility/Makefile.am Fri Oct 25 11:15:18 2013 +0100 @@ -6,6 +6,7 @@ user-defined-typo.tst brackets.tst single-quotes.tst grave-quotes.tst \ dashes.tst control-characters.tst unusual-characters.tst \ windows-1252.tst periods.tst long-line.tst unmarked-paragraph.tst \ + paranoid.tst paranoid-typos.tst no-paranoid.tst no-paranoid-typos.tst \ hebe-jeebies.tst mail-from.tst scannos.tst before-comma.tst \ before-period.tst double-punctuation.tst genitives.tst embedded-cr.tst \ continuing-quotes.tst diff -r f44c530f80da -r ff0aa9b1397a test/compatibility/no-paranoid-typos.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compatibility/no-paranoid-typos.tst Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,12 @@ +**************** OPTIONS **************** +-x +-t +**************** INPUT **************** +In paranoid mode we check for a standalone digits. 1 think this is a useful +feature. When checking for typos every, strangly placed comma is reported. + +If paranoid mode is switched off, we can still check for typos. +**************** EXPECTED **************** + +feature. When checking for typos every, strangly placed comma is reported. + Line 2 column 39 - Query punctuation after every? diff -r f44c530f80da -r ff0aa9b1397a test/compatibility/no-paranoid.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compatibility/no-paranoid.tst Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,8 @@ +**************** OPTIONS **************** +-x +**************** INPUT **************** +In paranoid mode we check for a standalone digits. 1 think this is a useful +feature. When checking for typos every, strangly placed comma is reported. + +If paranoid mode is switched off, checking for typos defaults to off too. +**************** EXPECTED **************** diff -r f44c530f80da -r ff0aa9b1397a test/compatibility/paranoid-typos.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compatibility/paranoid-typos.tst Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,12 @@ +**************** OPTIONS **************** +-t +**************** INPUT **************** +In paranoid mode we check for a standalone digits. 1 think this is a useful +feature. When checking for typos every, strangly placed comma is reported. + +In paranoid mode (the default), typo checking is switched off with its +short option. +**************** EXPECTED **************** + +In paranoid mode we check for a standalone digits. 1 think this is a useful + Line 1 column 51 - Query standalone 1 diff -r f44c530f80da -r ff0aa9b1397a test/compatibility/paranoid.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/compatibility/paranoid.tst Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,12 @@ +**************** INPUT **************** +In paranoid mode we check for a standalone digits. 1 think this is a useful +feature. When checking for typos every, strangly placed comma is reported. + +By default, both paranoid mode and checking for typos should be on. +**************** EXPECTED **************** + +In paranoid mode we check for a standalone digits. 1 think this is a useful + Line 1 column 51 - Query standalone 1 + +feature. When checking for typos every, strangly placed comma is reported. + Line 2 column 39 - Query punctuation after every? diff -r f44c530f80da -r ff0aa9b1397a test/harness/Makefile.am --- a/test/harness/Makefile.am Sat Oct 26 18:47:33 2013 +0100 +++ b/test/harness/Makefile.am Fri Oct 25 11:15:18 2013 +0100 @@ -5,5 +5,6 @@ loupe_test_SOURCES=loupe-test.c testcase.c testcase.h testcaseio.c \ testcaseio.h testcaseparser.c testcaseparser.h testcaseinput.c \ - testcaseinput.h warningsparser.c warningsparser.h + testcaseinput.h testcaseoutput.c testcaseoutput.h warningsparser.c \ + warningsparser.h loupe_test_LDADD=../../bl/libbl.la diff -r f44c530f80da -r ff0aa9b1397a test/harness/loupe-test.c --- a/test/harness/loupe-test.c Sat Oct 26 18:47:33 2013 +0100 +++ b/test/harness/loupe-test.c Fri Oct 25 11:15:18 2013 +0100 @@ -48,6 +48,7 @@ exit(1); } bl_set_print_handlers(); + g_setenv("BOOKLOUPE_CONFIG_PATH",".",TRUE); for(i=1;i #include "testcase.h" #include "testcaseinput.h" +#include "testcaseoutput.h" GQuark testcase_error_quark(void) { @@ -171,6 +172,64 @@ return g_string_free(filename,FALSE); } +/* + * Verify that all the output files specified by a testcase are present + * with the expected contents. + */ +gboolean testcase_verify_output_files(Testcase *testcase) +{ + GSList *link; + GError *tmp_err=NULL; + gboolean retval=TRUE; + ssize_t offset; + gchar *contents; + TestcaseOutput *output; + for(link=testcase->outputs;link;link=link->next) + { + output=link->data; + if (!testcase_output_read(testcase,output,&contents,NULL,&tmp_err)) + { + g_print("%s: FAIL\n",testcase->basename); + g_print("%s\n",tmp_err->message); + g_clear_error(&tmp_err); + retval=FALSE; + break; + } + else + { + if (strcmp(contents,output->contents)) + { + g_print("%s: FAIL\n",testcase->basename); + offset=common_prefix_length(contents,output->contents); + if (!offset && !contents[offset]) + g_print("%s: Unexpected empty output from bookloupe.\n", + output->name); + else + { + g_print("%s: Unexpected output from bookloupe:\n", + output->name); + print_unexpected(contents,offset); + } + retval=FALSE; + } + g_free(contents); + break; + } + } + for(link=testcase->outputs;link;link=link->next) + if (!testcase_output_remove(testcase,link->data,&tmp_err)) + { + if (retval) + { + g_print("%s: FAIL\n",testcase->basename); + g_print("%s\n",tmp_err->message); + retval=TRUE; + } + g_clear_error(&tmp_err); + } + return retval; +} + gboolean testcase_spawn_bookloupe(Testcase *testcase,char **standard_output, GError **error) { @@ -496,7 +555,7 @@ gboolean r; size_t pos,offset; GString *header; - char *output,*filename,*s,*summary,*xfail=NULL; + char *filename,*s,*summary,*xfail=NULL; GError *error=NULL; if (!testcase_create_input_files(testcase,&error)) { @@ -505,7 +564,7 @@ g_error_free(error); return FALSE; } - r=testcase_spawn_bookloupe(testcase,&output,&error); + r=testcase_spawn_bookloupe(testcase,&testcase->test_output,&error); if (!r) { g_print("%s: FAIL\n",testcase->basename); @@ -522,41 +581,47 @@ g_error_free(error); return FALSE; } - header=g_string_new("\n\nFile: "); - g_string_append(header,filename); - g_string_append(header,"\n"); - if (!g_str_has_prefix(output,header->str)) + if (testcase->expected || testcase->warnings) { - g_print("%s: FAIL\n",testcase->basename); - g_print("Unexpected header from bookloupe:\n"); - offset=common_prefix_length(output,header->str); - print_unexpected(output,offset); - r=FALSE; - } - pos=header->len; - if (r) - { - /* Find the end of the summary */ - s=strstr(output+pos,"\n\n"); - if (s) - { - summary=g_strndup(output+pos,s-(output+pos)); - r=testcase_check_summary(testcase,summary); - g_free(summary); - pos=s-output+2; - } - else + header=g_string_new("\n\nFile: "); + g_string_append(header,filename); + g_string_append(header,"\n"); + if (!g_str_has_prefix(testcase->test_output,header->str)) { g_print("%s: FAIL\n",testcase->basename); - g_print("Unterminated summary from bookloupe:\n%s\n",output+pos); + g_print("Unexpected header from bookloupe:\n"); + offset=common_prefix_length(testcase->test_output,header->str); + print_unexpected(testcase->test_output,offset); r=FALSE; } + summary=testcase->test_output+header->len; + pos=header->len; + if (r) + { + /* Find the end of the summary */ + s=strstr(summary,"\n\n"); + if (s) + { + summary=g_strndup(summary,s-summary); + r=testcase_check_summary(testcase,summary); + g_free(summary); + pos=s-testcase->test_output+2; + } + else + { + g_print("%s: FAIL\n",testcase->basename); + g_print("Unterminated summary from bookloupe:\n%s\n",summary); + r=FALSE; + } + } + g_string_free(header,TRUE); + if (r) + r=testcase_check_warnings(testcase,testcase->test_output+pos, + &xfail); } - g_string_free(header,TRUE); - if (r) - r=testcase_check_warnings(testcase,output+pos,&xfail); + if (!testcase_verify_output_files(testcase)) + r=FALSE; g_free(filename); - g_free(output); if (r) { if (xfail) @@ -617,5 +682,6 @@ g_slist_free(testcase->warnings); g_free(testcase->encoding); g_strfreev(testcase->options); + g_free(testcase->test_output); g_free(testcase); } diff -r f44c530f80da -r ff0aa9b1397a test/harness/testcase.h --- a/test/harness/testcase.h Sat Oct 26 18:47:33 2013 +0100 +++ b/test/harness/testcase.h Fri Oct 25 11:15:18 2013 +0100 @@ -41,11 +41,13 @@ char *basename; char *tmpdir; GSList *inputs; + GSList *outputs; char *expected; TestcaseSummary summary; GSList *warnings; char *encoding; /* The character encoding to talk to BOOKLOUPE in */ char **options; + char *test_output; enum { TESTCASE_XFAIL=1<<0, TESTCASE_TMP_DIR=1<<1, diff -r f44c530f80da -r ff0aa9b1397a test/harness/testcaseio.c --- a/test/harness/testcaseio.c Sat Oct 26 18:47:33 2013 +0100 +++ b/test/harness/testcaseio.c Fri Oct 25 11:15:18 2013 +0100 @@ -5,6 +5,7 @@ #include #include "testcaseparser.h" #include "testcaseinput.h" +#include "testcaseoutput.h" #include "testcaseio.h" #include "warningsparser.h" @@ -70,6 +71,25 @@ else if (!testcase->expected && !testcase->warnings && !strcmp(tag,"EXPECTED")) testcase->expected=g_strdup(text); + else if (g_str_has_prefix(tag,"EXPECTED(") && tag[strlen(tag)-1]==')') + { + arg=g_strndup(tag+9,strlen(tag)-10); + s=g_path_get_dirname(arg); + if (strcmp(s,".")) + { + g_printerr("%s: Expected files may not have a " + "directory component\n",arg); + g_free(s); + g_free(arg); + testcase_free(testcase); + testcase_parser_free(parser); + return NULL; + } + g_free(s); + testcase->outputs=g_slist_prepend(testcase->outputs, + testcase_output_new(arg,text)); + g_free(arg); + } else if (!testcase->expected && !testcase->warnings && !strcmp(tag,"WARNINGS")) { @@ -108,11 +128,14 @@ } g_free(s); } - else if (!testcase->encoding && !strcmp(tag,"OPTIONS")) + else if (!testcase->options && !strcmp(tag,"OPTIONS")) { testcase->options=g_strsplit(text,"\n",0); - g_free(testcase->options[g_strv_length(testcase->options)-1]); - testcase->options[g_strv_length(testcase->options)-1]=NULL; + if (testcase->options && g_strv_length(testcase->options)>0) + { + g_free(testcase->options[g_strv_length(testcase->options)-1]); + testcase->options[g_strv_length(testcase->options)-1]=NULL; + } } else { diff -r f44c530f80da -r ff0aa9b1397a test/harness/testcaseoutput.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/harness/testcaseoutput.c Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include "testcase.h" +#include "testcaseoutput.h" + +/* + * Replace \r\n with \n, \n with U+240A (visible symbol for LF) + * and \r with U+240D (visible symbol for CR). + */ +static char *dos2unix(const char *text) +{ + gunichar c; + gboolean cr=FALSE; + const gunichar visible_lf=0x240A; + const gunichar visible_cr=0x240D; + GString *string; + string=g_string_new(NULL); + while(*text) + { + c=g_utf8_get_char(text); + text=g_utf8_next_char(text); + if (cr) + { + cr=FALSE; + if (c=='\n') + { + g_string_append_c(string,'\n'); + continue; + } + else + g_string_append_unichar(string,visible_cr); + } + if (c=='\r') + cr=TRUE; + else if (c=='\n') + g_string_append_unichar(string,visible_lf); + else + g_string_append_unichar(string,c); + } + if (cr) + g_string_append_unichar(string,visible_cr); + return g_string_free(string,FALSE); +} + +/* + * Read an output file needed for a testcase (as specified in ). + * The file is read in the encoding specified for communicating with + * bookloupe. + */ +gboolean testcase_output_read(Testcase *testcase,TestcaseOutput *output, + gchar **contents,gsize *length,GError **error) +{ + char *filename,*s,*t; + gboolean retval; + GError *tmp_err=NULL; + if (!strcmp(output->name,"stdout")) + { + *contents=g_strdup(testcase->test_output); + if (length) + *length=strlen(testcase->test_output); + } + else + { + if (testcase->tmpdir) + filename=g_build_filename(testcase->tmpdir,output->name,NULL); + else + filename=g_strdup(output->name); + if (!g_file_get_contents(filename,&s,NULL,error)) + { + g_free(filename); + return FALSE; + } + g_free(filename); + if (testcase->encoding) + { + t=dos2unix(s); + g_free(s); + s=g_convert(t,-1,"UTF-8",testcase->encoding,NULL,length,&tmp_err); + g_free(t); + if (!s) + { + g_propagate_prefixed_error(error,tmp_err, + "Conversion from %s failed: ",testcase->encoding); + return FALSE; + } + *contents=s; + } + else + { + *contents=dos2unix(s); + if (length) + *length=strlen(*contents); + } + } + return TRUE; +} + +/* + * Remove an output file created by program under test. + */ +gboolean testcase_output_remove(Testcase *testcase,TestcaseOutput *output, + GError **error) +{ + char *filename; + if (!strcmp(output->name,"stdout")) + return TRUE; + if (testcase->tmpdir) + filename=g_build_filename(testcase->tmpdir,output->name,NULL); + else + filename=g_strdup(output->name); + if (g_unlink(filename)<0) + { + g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno), + "%s: %s",filename,g_strerror(errno)); + return FALSE; + } + g_free(filename); + return TRUE; +} + +/* Create a new description of an output file expected by a testcase */ +TestcaseOutput *testcase_output_new(const char *name,const char *contents) +{ + TestcaseOutput *output; + output=g_new0(TestcaseOutput,1); + output->name=g_strdup(name); + output->contents=g_strdup(contents); + return output; +} + +/* Free the description of a testcase output file */ +void testcase_output_free(TestcaseOutput *output) +{ + g_free(output->name); + g_free(output->contents); + g_free(output); +} diff -r f44c530f80da -r ff0aa9b1397a test/harness/testcaseoutput.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/harness/testcaseoutput.h Fri Oct 25 11:15:18 2013 +0100 @@ -0,0 +1,19 @@ +#ifndef TESTCASE_OUTPUT_H +#define TESTCASE_OUTPUT_H + +#include +#include "testcase.h" + +typedef struct { + char *name; + char *contents; +} TestcaseOutput; + +gboolean testcase_output_read(Testcase *testcase,TestcaseOutput *output, + gchar **contents,gsize *length,GError **error); +gboolean testcase_output_remove(Testcase *testcase,TestcaseOutput *output, + GError **error); +TestcaseOutput *testcase_output_new(const char *name,const char *contents); +void testcase_output_free(TestcaseOutput *output); + +#endif /* TESTCASE_OUTPUT_H */