Merge branch 'staging/feature/wml-error-report-improvements'

This commit is contained in:
Ignacio R. Morelle 2014-02-13 06:26:05 -03:00
commit 37af8d2b08
12 changed files with 580 additions and 34 deletions

View file

@ -96,6 +96,7 @@
#enddef
{_GUI_DEFINITION "default" "default label" DEFAULT () DEFAULT ({GUI__TEXT_VERTICALLY_CENTRED})}
{_GUI_DEFINITION "default_bold" "default label, bold font" DEFAULT "bold" DEFAULT ({GUI__TEXT_VERTICALLY_CENTRED})}
{_GUI_DEFINITION "scroll_label" "scroll label" DEFAULT () DEFAULT 0}
{_GUI_DEFINITION "title" "label used for titles" TITLE "bold" TITLE ({GUI__TEXT_VERTICALLY_CENTRED})}
{_GUI_DEFINITION "default_large" "default, large font size" LARGE () DEFAULT ({GUI__TEXT_VERTICALLY_CENTRED})}

View file

@ -0,0 +1,173 @@
#textdomain wesnoth-lib
###
### Dialog used to report WML parser or preprocessor errors.
###
[window]
id = "wml_error"
description = "WML preprocessor/parser error report dialog."
[resolution]
definition = "default"
#click_dismiss = "true"
maximum_width = 600
maximum_height = 600
automatic_placement = "true"
vertical_placement = "center"
horizontal_placement = "center"
[tooltip]
id = "tooltip_large"
[/tooltip]
[helptip]
id = "tooltip_large"
[/helptip]
[grid]
[row]
[column]
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
definition = "title"
label = _ "Error"
[/label]
[/column]
[/row]
[row]
[column]
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
id = "summary"
definition = "default"
wrap = true
[/label]
[/column]
[/row]
[row]
[column]
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
id = "files"
definition = "default"
wrap = true
[/label]
[/column]
[/row]
[row]
[column]
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
id = "post_summary"
definition = "default"
wrap = true
[/label]
[/column]
[/row]
[row]
[column]
horizontal_grow = "true"
[grid]
[row]
[column]
border = "all"
border_size = 5
horizontal_alignment = "left"
vertical_alignment = "bottom"
[label]
id = "details_heading"
definition = "default_bold"
label = _ "Details:"
[/label]
[/column]
[column]
border = "top,left,right"
border_size = 5
horizontal_alignment = "right"
[button]
id = "copy"
definition = "action_copy"
label = _ "clipboard^Copy"
tooltip = _ "Copy this report to clipboard"
[/button]
[/column]
[/row]
[/grid]
[/column]
[/row]
[row]
[column]
border = "all"
border_size = 5
horizontal_grow = "true"
[scroll_label]
id = "details"
definition = "description"
[/scroll_label]
[/column]
[/row]
[row]
[column]
border = "all"
border_size = 5
horizontal_alignment = "right"
[button]
id = "ok"
definition = "default"
label = _ "OK"
[/button]
[/column]
[/row]
[/grid]
[/resolution]
[/window]

View file

@ -769,6 +769,7 @@ set(wesnoth-main_SRC
gui/dialogs/transient_message.cpp
gui/dialogs/unit_attack.cpp
gui/dialogs/unit_create.cpp
gui/dialogs/wml_error.cpp
gui/dialogs/wml_message.cpp
halo.cpp
help.cpp

View file

@ -395,6 +395,7 @@ wesnoth_sources = Split("""
gui/dialogs/transient_message.cpp
gui/dialogs/unit_attack.cpp
gui/dialogs/unit_create.cpp
gui/dialogs/wml_error.cpp
gui/dialogs/wml_message.cpp
gui/lib/types/point.cpp
gui/widgets/button.cpp

View file

@ -466,6 +466,10 @@ static int do_gameloop(int argc, char** argv)
const cursor::manager cursor_manager;
cursor::set(cursor::WAIT);
#if (defined(_X11) && !defined(__APPLE__)) || defined(_WIN32)
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
#endif
loadscreen::global_loadscreen_manager loadscreen_manager(game->disp().video());
loadscreen::start_stage("init gui");
@ -492,10 +496,6 @@ static int do_gameloop(int argc, char** argv)
loadscreen::start_stage("refresh addons");
refresh_addon_version_info_cache();
#if (defined(_X11) && !defined(__APPLE__)) || defined(_WIN32)
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
#endif
config tips_of_day;
loadscreen::start_stage("titlescreen");

View file

@ -20,13 +20,14 @@
#include "cursor.hpp"
#include "game_config.hpp"
#include "gettext.hpp"
#include "gui/dialogs/message.hpp"
#include "gui/dialogs/wml_error.hpp"
#include "language.hpp"
#include "loadscreen.hpp"
#include "log.hpp"
#include "resources.hpp"
#include "scripting/lua.hpp"
#include "hotkey/hotkey_item.hpp"
#include "hotkey/hotkey_command.hpp"
#include <boost/foreach.hpp>
@ -174,10 +175,10 @@ void game_config_manager::load_game_config(FORCE_RELOAD_CONFIG force_reload,
::init_strings(game_config());
theme::set_known_themes(&game_config());
} catch(game::error& e) {
ERR_CONFIG << "Error loading game configuration files\n";
gui2::show_error_message(disp_.video(),
_("Error loading game configuration files: '") +
e.message + _("' (The game will now exit)"));
ERR_CONFIG << "Error loading game configuration files\n" << e.message << '\n';
gui2::twml_error::display(
_("Error loading game configuration files. The game will now exit."),
e.message, disp_.video());
throw;
}
@ -198,7 +199,8 @@ void game_config_manager::load_addons_cfg()
get_files_in_dir(user_campaign_dir, &user_files, &user_dirs,
ENTIRE_FILE_PATH);
std::stringstream user_error_log;
std::vector<std::string> error_log;
// Append the $user_campaign_dir/*.cfg files to addons_to_load.
BOOST_FOREACH(const std::string& uc, user_files) {
@ -229,11 +231,11 @@ void game_config_manager::load_addons_cfg()
ERR_CONFIG << "error reading usermade add-on '"
<< file << "'\n";
error_addons.push_back(file);
user_error_log << "The format '~" << file.substr(userdata_loc)
<< "' is only for single-file add-ons, use '~"
<< file.substr(userdata_loc,
error_log.push_back("The format '~" + file.substr(userdata_loc)
+ "' is only for single-file add-ons, use '~"
+ file.substr(userdata_loc,
size_minus_extension - userdata_loc)
<< "/_main.cfg' instead.\n";
+ "/_main.cfg' instead.");
}
else {
addons_to_load.push_back(file);
@ -258,29 +260,34 @@ void game_config_manager::load_addons_cfg()
game_config_.append(umc_cfg);
} catch(config::error& err) {
ERR_CONFIG << "error reading usermade add-on '" << uc << "'\n";
ERR_CONFIG << err.message << '\n';
error_addons.push_back(uc);
user_error_log << err.message << "\n";
error_log.push_back(err.message);
} catch(preproc_config::error& err) {
ERR_CONFIG << "error reading usermade add-on '" << uc << "'\n";
ERR_CONFIG << err.message << '\n';
error_addons.push_back(uc);
user_error_log << err.message << "\n";
error_log.push_back(err.message);
} catch(io_exception&) {
ERR_CONFIG << "error reading usermade add-on '" << uc << "'\n";
error_addons.push_back(uc);
}
}
if(error_addons.empty() == false) {
std::stringstream msg;
msg << _n("The following add-on had errors and could not be loaded:",
"The following add-ons had errors and could not be loaded:",
error_addons.size());
BOOST_FOREACH(const std::string& error_addon, error_addons) {
msg << "\n" << error_addon;
}
const size_t n = error_addons.size();
const std::string& msg1 =
_n("The following add-on had errors and could not be loaded:",
"The following add-ons had errors and could not be loaded:",
n);
const std::string& msg2 =
_n("Please report this to the author or maintainer of this add-on.",
"Please report this to the respective authors or maintainers of these add-ons.",
n);
msg << '\n' << _("ERROR DETAILS:") << '\n' << user_error_log.str();
const std::string& report = utils::join(error_log, "\n\n");
gui2::show_error_message(disp_.video(),msg.str());
gui2::twml_error::display(msg1, msg2, error_addons, report,
disp_.video());
}
}

View file

@ -0,0 +1,236 @@
/*
Copyright (C) 2014 by Ignacio R. Morelle <shadowm2006@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
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.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/dialogs/wml_error.hpp"
#include "addon/info.hpp"
#include "addon/manager.hpp"
#include "clipboard.hpp"
#include "filesystem.hpp"
#include "gettext.hpp"
#include "gui/auxiliary/find_widget.tpp"
#include "gui/widgets/button.hpp"
#include "gui/widgets/control.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "serialization/string_utils.hpp"
#include <boost/bind.hpp>
namespace
{
inline bool is_dir_separator(char c)
{
#ifdef _WIN32
return c == '/' || c == '\\';
#else
return c == '/';
#endif
}
void strip_trailing_dir_separators(std::string& str)
{
while(is_dir_separator(str[str.size() - 1])) {
str.erase(str.size() - 1);
}
}
std::string format_file_list(const std::vector<std::string>& files_original)
{
if(files_original.empty()) {
return "";
}
const std::string& addons_path = get_addon_campaigns_dir();
std::vector<std::string> files(files_original);
BOOST_FOREACH(std::string& file, files)
{
std::string base;
std::string filename = file_name(file);
std::string parent_path;
const bool is_main_cfg = filename == "_main.cfg";
if(is_main_cfg) {
parent_path = directory_name(file) + "/..";
} else {
parent_path = directory_name(file);
}
// Only proceed to pretty-format the filename if it's from the add-ons
// directory.
if(normalize_path(parent_path) != normalize_path(addons_path)) {
continue;
}
if(is_main_cfg) {
base = directory_name(file);
// HACK: fool file_name() into giving us the parent directory name
// alone by making base seem not like a directory path,
// otherwise it returns an empty string.
strip_trailing_dir_separators(base);
base = file_name(base);
} else {
base = filename;
}
if(base.empty()) {
// We did something wrong. In the interest of not messing up the
// report, leave the original filename intact.
continue;
}
//
// Display the name as an add-on name instead of a filename.
//
if(!is_main_cfg) {
// Remove the file extension first.
static const std::string wml_suffix = ".cfg";
if(base.size() > wml_suffix.size()) {
const size_t suffix_pos = base.size() - wml_suffix.size();
if(base.substr(suffix_pos) == wml_suffix) {
base.erase(suffix_pos);
}
}
}
if(have_addon_install_info(base)) {
// _info.cfg may have the add-on's title starting with 1.11.7,
// if the add-on was downloaded using the revised _info.cfg writer.
config cfg;
get_addon_install_info(base, cfg);
const config& info_cfg = cfg.child("info");
if(info_cfg) {
file = info_cfg["title"].str();
continue;
}
}
// Fallback to using a synthetic title with underscores replaced with
// whitespace.
file = make_addon_title(base);
}
if(files.size() == 1) {
return utils::indent(files.front());
}
return utils::bullet_list(files);
}
}
namespace gui2
{
/*WIKI
* @page = GUIWindowDefinitionWML
* @order = 2_wml_error
*
* == WML error ==
*
* Dialog used to report WML parser or preprocessor errors.
*
* @begin{table}{dialog_widgets}
*
* summary & & control & m &
* Label used for displaying a brief summary of the error(s). $
*
* files & & control & m &
* Label used to display the list of affected add-ons or files, if
* applicable. It is hidden otherwise. It is recommended to place it
* after the summary label. $
*
* post_summary & & control & m &
* Label used for displaying instructions for reporting the error.
* It is recommended to place it after the file list label. It may be
* hidden if empty. $
*
* details & & control & m &
* Full report of the parser or preprocessor error(s) found. $
*
* copy & & button & m &
* Button that the user can click on to copy the error report to the
* system clipboard. $
*
* @end{table}
*/
REGISTER_DIALOG(wml_error)
twml_error::twml_error(const std::string& summary,
const std::string& post_summary,
const std::vector<std::string>& files,
const std::string& details)
: have_files_(!files.empty())
, have_post_summary_(!post_summary.empty())
, report_()
{
const std::string& file_list_text = format_file_list(files);
report_ = summary;
if(!file_list_text.empty()) {
report_ += "\n" + file_list_text;
}
if(!post_summary.empty()) {
report_ += "\n\n" + post_summary;
}
report_ += "\n\n";
report_ += _("Details:");
report_ += "\n\n" + utils::indent(details);
// Since this is likely to be pasted into a text file, add a final line
// break for convenience, since otherwise the report ends mid-line.
report_ += "\n";
register_label("summary", true, summary);
register_label("post_summary", true, post_summary);
register_label("files", true, file_list_text);
register_label("details", true, details);
}
void twml_error::pre_show(CVideo& /*video*/, twindow& window)
{
if(!have_files_) {
tcontrol& filelist = find_widget<tcontrol>(&window, "files", false);
filelist.set_visible(tcontrol::tvisible::invisible);
}
if(!have_post_summary_) {
tcontrol& post_summary = find_widget<tcontrol>(&window,
"post_summary", false);
post_summary.set_visible(tcontrol::tvisible::invisible);
}
tbutton& copy_button = find_widget<tbutton>(&window, "copy", false);
connect_signal_mouse_left_click(
copy_button, boost::bind(&twml_error::copy_report_callback, this));
}
void twml_error::copy_report_callback()
{
copy_to_clipboard(report_, false);
}
} // end namespace gui2

View file

@ -0,0 +1,74 @@
/*
Copyright (C) 2014 by Ignacio R. Morelle <shadowm2006@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
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.
See the COPYING file for more details.
*/
#ifndef GUI_DIALOGS_WML_ERROR_HPP_INCLUDED
#define GUI_DIALOGS_WML_ERROR_HPP_INCLUDED
#include "gui/dialogs/dialog.hpp"
namespace gui2 {
/** WML preprocessor/parser error report dialog. */
class twml_error : public tdialog
{
public:
/**
* Constructor.
*
* @param summary Leading summary line for the report.
* @param post_summary Additional line with instructions for the user, may
* be empty.
* @param files List of WML files on which errors were detected.
* @param details Detailed WML preprocessor/parser error report.
*/
twml_error(const std::string& summary,
const std::string& post_summary,
const std::vector<std::string>& files,
const std::string& details);
/** The display function; see @ref tdialog for more information. */
static void display(const std::string& summary,
const std::string& post_summary,
const std::vector<std::string>& files,
const std::string& details,
CVideo& video)
{
twml_error(summary, post_summary, files, details).show(video);
}
/** The display function; see @ref tdialog for more information. */
static void display(const std::string& summary,
const std::string& details,
CVideo& video)
{
display(summary, "", std::vector<std::string>(), details, video);
}
private:
bool have_files_;
bool have_post_summary_;
std::string report_; // Plain text report for copying to clipboard.
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
virtual const std::string& window_id() const;
/** Inherited from tdialog. */
void pre_show(CVideo& video, twindow& window);
void copy_report_callback();
};
} // end namespace gui2
#endif

View file

@ -64,7 +64,7 @@ private:
void parse_element();
void parse_variable();
std::string lineno_string(utils::string_map &map, std::string const &lineno,
const char *error_string);
const char *error_string, const char *hint_string = NULL);
void error(const std::string& message);
config& cfg_;
@ -142,7 +142,8 @@ void parser::operator()()
std::stringstream ss;
ss << elements.top().start_line << " " << elements.top().file;
error(lineno_string(i18n_symbols, ss.str(),
N_("Missing closing tag for tag [$tag] at $pos")));
N_("Missing closing tag for tag [$tag]"),
N_("at $pos")));
}
}
@ -205,7 +206,8 @@ void parser::parse_element()
std::stringstream ss;
ss << elements.top().start_line << " " << elements.top().file;
error(lineno_string(i18n_symbols, ss.str(),
N_("Found invalid closing tag [/$tag2] for tag [$tag1] (opened at $pos)")));
N_("Found invalid closing tag [/$tag2] for tag [$tag1]"),
N_("opened at $pos")));
}
if(validator_){
element & el= elements.top();
@ -339,10 +341,18 @@ void parser::parse_variable()
* This function is crap. Don't use it on a string_map with prefixes.
*/
std::string parser::lineno_string(utils::string_map &i18n_symbols,
std::string const &lineno, const char *error_string)
std::string const &lineno,
const char *error_string,
const char *hint_string)
{
i18n_symbols["pos"] = ::lineno_string(lineno);
std::string result = _(error_string);
if(hint_string != NULL) {
result += "\n ";
result += hint_string;
}
BOOST_FOREACH(utils::string_map::value_type& var, i18n_symbols)
boost::algorithm::replace_all(result, std::string("$") + var.first, std::string(var.second));
return result;
@ -352,18 +362,18 @@ void parser::error(const std::string& error_type)
{
utils::string_map i18n_symbols;
i18n_symbols["error"] = error_type;
i18n_symbols["value"] = tok_->current_token().value;
std::stringstream ss;
ss << tok_->get_start_line() << " " << tok_->get_file();
#ifdef DEBUG
i18n_symbols["value"] = tok_->current_token().value;
i18n_symbols["previous_value"] = tok_->previous_token().value;
throw config::error(
lineno_string(i18n_symbols, ss.str(),
N_("$error, value '$value', previous '$previous_value' at $pos")));
N_("$error"), N_("at $pos (value '$value', previous '$previous_value')")));
#else
throw config::error(
lineno_string(i18n_symbols, ss.str(),
N_("$error, value '$value' at $pos")));
N_("$error"), N_("at $pos")));
#endif
}

View file

@ -55,6 +55,8 @@ static t_file_number_map file_number_map;
static bool encode_filename = true;
static std::string preprocessor_error_detail_prefix = "\n ";
// get filename associated to this code
static std::string get_filename(const std::string& file_code){
if(!encode_filename)
@ -326,7 +328,8 @@ std::string lineno_string(const std::string &lineno)
{
std::vector< std::string > pos = utils::quoted_split(lineno, ' ');
std::vector< std::string >::const_iterator i = pos.begin(), end = pos.end();
std::string included_from = " included from ";
std::string included_from =
preprocessor_error_detail_prefix + "included from ";
std::string res;
while (i != end) {
std::string const &line = *(i++);
@ -348,7 +351,7 @@ void preprocessor_streambuf::error(const std::string& error_type, int l)
std::ostringstream pos;
pos << l << ' ' << location_;
position = lineno_string(pos.str());
error = error_type + " at " + position;
error = error_type + preprocessor_error_detail_prefix + "at " + position;
ERR_CF << error << '\n';
throw preproc_config::error(error);
}

View file

@ -741,6 +741,35 @@ bool wildcard_string_match(const std::string& str, const std::string& match) {
return matches;
}
std::string indent(const std::string& string, size_t indent_size)
{
if(string.empty() || indent_size == 0) {
return "";
}
const std::string indent(indent_size, ' ');
const std::vector<std::string>& lines = split(string, '\x0A', 0);
std::string res;
for(size_t lno = 0; lno < lines.size();) {
const std::string& line = lines[lno];
// Lines containing only a carriage return count as empty.
if(!line.empty() && line != "\x0D") {
res += indent;
}
res += line;
if(++lno < lines.size()) {
res += '\x0A';
}
}
return res;
}
std::vector< std::string > quoted_split(std::string const &val, char c, int flags, char quote)
{
std::vector<std::string> res;

View file

@ -191,6 +191,17 @@ std::string bullet_list(const T& v, size_t indent = 4, const std::string& bullet
return str.str();
}
/**
* Indent a block of text.
*
* Only lines with content are changed; empty lines are left intact.
*
* @param string Text to indent.
* @param indent_size Number of indentation units to use.
* @param indent_unit Indentation unit.
*/
std::string indent(const std::string& string, size_t indent_size = 4);
/**
* This function is identical to split(), except it does not split
* when it otherwise would if the previous character was identical to the parameter 'quote'.