X-Git-Url: http://nitlanguage.org diff --git a/src/toolcontext.nit b/src/toolcontext.nit index 021c2c7..8196aeb 100644 --- a/src/toolcontext.nit +++ b/src/toolcontext.nit @@ -41,9 +41,21 @@ class Message # The human-readable description of the message. # - # It should be short and fit on a single line. - # It should also have meaningful information first in case - # on truncation by an IDE for instance. + # eg. "Error: cannot find method `foo`." + # + # A good message should: + # + # * start with a message type like "Error:", "Syntax Error:", "Warning:". + # The type is capitalized and terminated by a column. + # The rest on the message starts with a lowercase letter and is terminated with a dot. + # + # * be short and fit on a single line. + # + # * have meaningful information first. + # This helps the reader and remain usable + # when truncated, by an IDE for instance. + # + # * enclose identifiers, keywords and pieces of code with back-quotes. var text: String # Comparisons are made on message locations. @@ -54,6 +66,11 @@ class Message return location.as(not null) < other.location.as(not null) end + redef fun ==(other): Bool do + if not other isa Message then return false + return location == other.location and tag == other.tag and text == other.text + end + redef fun to_s: String do var l = location @@ -91,6 +108,24 @@ class Message end end +redef class Location + # Errors and warnings associated to this location. + var messages: nullable Array[Message] + + # Add a message to `self` + # + # See `messages` + private fun add_message(m: Message) + do + var ms = messages + if ms == null then + ms = new Array[Message] + messages = ms + end + ms.add m + end +end + # Global context for tools class ToolContext # Number of errors @@ -102,6 +137,9 @@ class ToolContext # Directory where to generate log files var log_directory: String = "logs" + # Stream in `log_directory` where all info messages are written + var log_info: nullable Writer = null + # Messages private var messages = new Array[Message] private var message_sorter: Comparator = default_comparator @@ -131,13 +169,13 @@ class ToolContext return tags.has("all") or tags.has(tag) end - # Output all current stacked messages and display total error informations + # Output all current stacked messages # # Return true if no errors occurred. # # If some errors occurred, the behavior depends on the value of `keep_going`. - # If `keep_going` is false, then the program exits. - # Else, the error count and the warning count are reset and false is returned. + # If `keep_going` is false, then the total error informations is displayed and the program exits. + # Else, false is returned. fun check_errors: Bool do if messages.length > 0 then @@ -155,32 +193,40 @@ class ToolContext end if error_count > 0 then - errors_info - if not keep_going then exit(1) + if not keep_going then + errors_info + exit(1) + end return false end return true end - # Display (and reset) total error informations + # Display total error informations fun errors_info do if error_count == 0 and warning_count == 0 then return if opt_no_color.value then return sys.stderr.write "Errors: {error_count}. Warnings: {warning_count}.\n" - error_count = 0 - warning_count = 0 end # Display an error - fun error(l: nullable Location, s: String) + # + # Return the message (to add information) + fun error(l: nullable Location, s: String): Message do - messages.add(new Message(l,null,s)) + var m = new Message(l,null,s) + if messages.has(m) then return m + if l != null then l.add_message m + messages.add m error_count = error_count + 1 if opt_stop_on_first_error.value then check_errors + return m end # Add an error, show errors and quit + # + # Because the program will quit, nothing is returned. fun fatal_error(l: nullable Location, s: String) do error(l,s) @@ -197,14 +243,20 @@ class ToolContext # * They always are real issues (no false positive) # # First-level warnings are displayed by default (except if option `-q` is given). - fun warning(l: nullable Location, tag: String, text: String) + # + # Return the message (to add information) or null if the warning is disabled + fun warning(l: nullable Location, tag: String, text: String): nullable Message do - if opt_warning.value.has("no-{tag}") then return - if not opt_warning.value.has(tag) and opt_warn.value == 0 then return - if is_warning_blacklisted(l, tag) then return - messages.add(new Message(l, tag, text)) + if opt_warning.value.has("no-{tag}") then return null + if not opt_warning.value.has(tag) and opt_warn.value == 0 then return null + if is_warning_blacklisted(l, tag) then return null + var m = new Message(l, tag, text) + if messages.has(m) then return null + if l != null then l.add_message m + messages.add m warning_count = warning_count + 1 if opt_stop_on_first_error.value then check_errors + return m end # Display a second-level warning. @@ -220,14 +272,20 @@ class ToolContext # # In order to prevent warning inflation à la Java, second-level warnings are not displayed by # default and require an additional option `-W`. - fun advice(l: nullable Location, tag: String, text: String) + # + # Return the message (to add information) or null if the warning is disabled + fun advice(l: nullable Location, tag: String, text: String): nullable Message do - if opt_warning.value.has("no-{tag}") then return - if not opt_warning.value.has(tag) and opt_warn.value <= 1 then return - if is_warning_blacklisted(l, tag) then return - messages.add(new Message(l, tag, text)) + if opt_warning.value.has("no-{tag}") then return null + if not opt_warning.value.has(tag) and opt_warn.value <= 1 then return null + if is_warning_blacklisted(l, tag) then return null + var m = new Message(l, tag, text) + if messages.has(m) then return null + if l != null then l.add_message m + messages.add m warning_count = warning_count + 1 if opt_stop_on_first_error.value then check_errors + return m end # Display an info @@ -236,6 +294,10 @@ class ToolContext if level <= verbose_level then print "{s}" end + if log_info != null then + log_info.write s + log_info.write "\n" + end end # Executes a program while checking if it's available and if the execution ended correctly @@ -249,7 +311,7 @@ class ToolContext args.remove_at 0 # Is the wanted program available? - var proc_which = new IProcess.from_a("which", [prog]) + var proc_which = new ProcessReader.from_a("which", [prog]) proc_which.wait var res = proc_which.status if res != 0 then @@ -303,6 +365,9 @@ class ToolContext # Option --stop-on-first-error var opt_stop_on_first_error = new OptionBool("Stop on first error", "--stop-on-first-error") + # Option --keep-going + var opt_keep_going = new OptionBool("Continue after errors, whatever the consequences", "--keep-going") + # Option --no-color var opt_no_color = new OptionBool("Do not use color to display errors and warnings", "--no-color") @@ -317,7 +382,7 @@ class ToolContext init do - option_context.add_option(opt_warn, opt_warning, opt_quiet, opt_stop_on_first_error, opt_no_color, opt_log, opt_log_dir, opt_nit_dir, opt_help, opt_version, opt_set_dummy_tool, opt_verbose, opt_bash_completion, opt_stub_man) + option_context.add_option(opt_warn, opt_warning, opt_quiet, opt_stop_on_first_error, opt_keep_going, opt_no_color, opt_log, opt_log_dir, opt_nit_dir, opt_help, opt_version, opt_set_dummy_tool, opt_verbose, opt_bash_completion, opt_stub_man) # Hide some internal options opt_stub_man.hidden = true @@ -423,14 +488,18 @@ The Nit language documentation and the source code of its tools and libraries ma # Set verbose level verbose_level = opt_verbose.value + if opt_keep_going.value then keep_going = true + if self.opt_quiet.value then self.opt_warn.value = 0 if opt_log_dir.value != null then log_directory = opt_log_dir.value.as(not null) if opt_log.value then # Make sure the output directory exists log_directory.mkdir - end + # Redirect the verbose messages + log_info = (log_directory/"info.txt").to_path.open_wo + end end # Get the current `nit_version` or "DUMMY_VERSION" if `--set-dummy-tool` is set. @@ -458,7 +527,7 @@ The Nit language documentation and the source code of its tools and libraries ma var res = opt_nit_dir.value if res != null then if not check_nit_dir(res) then - fatal_error(null, "Fatal Error: the value of --nit-dir does not seem to be a valid base Nit directory: {res}") + fatal_error(null, "Fatal Error: the value of --nit-dir does not seem to be a valid base Nit directory: {res}.") end return res end @@ -467,7 +536,7 @@ The Nit language documentation and the source code of its tools and libraries ma res = "NIT_DIR".environ if not res.is_empty then if not check_nit_dir(res) then - fatal_error(null, "Fatal Error: the value of NIT_DIR does not seem to be a valid base Nit directory: {res}") + fatal_error(null, "Fatal Error: the value of NIT_DIR does not seem to be a valid base Nit directory: {res}.") end return res end @@ -491,7 +560,7 @@ The Nit language documentation and the source code of its tools and libraries ma if check_nit_dir(res) then return res.simplify_path end - fatal_error(null, "Fatal Error: Cannot locate a valid base nit directory. It is quite unexpected. Try to set the environment variable `NIT_DIR` or to use the `--nit-dir` option.") + fatal_error(null, "Fatal Error: cannot locate a valid base Nit directory. It is quite unexpected. Try to set the environment variable `NIT_DIR` or to use the `--nit-dir` option.") abort end