import location
import version
import template
+import more_collections
# A warning or an error
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.
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
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
# 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
- # Output all current stacked messages.
- # If some errors occurred, exits the program.
- fun check_errors
+ # Does an error prevent the program to stop at `check_errors`?
+ #
+ # Default to false.
+ # Set this value to `true` if you need to keep the program going in case of error.
+ var keep_going = false is writable
+
+ # List of tags per source-file whose warnings are not displayed.
+ #
+ # Initially empty, it is up to the toll to fill it.
+ # The tag "all" means all warnings and advices.
+ var warning_blacklist = new MultiHashMap[SourceFile, String]
+
+ # Is the source-file of `l` associated with `tag` in `warning_blacklist`?
+ #
+ # currently returns `false` if `l` is null or does not have a source-file.
+ fun is_warning_blacklisted(l: nullable Location, tag: String): Bool
+ do
+ if l == null then return false
+ var f = l.file
+ if f == null then return false
+ var tags = warning_blacklist.get_or_null(f)
+ if tags == null then return false
+ return tags.has("all") or tags.has(tag)
+ end
+
+ # 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 total error informations is displayed and the program exits.
+ # Else, false is returned.
+ fun check_errors: Bool
do
if messages.length > 0 then
message_sorter.sort(messages)
end
if error_count > 0 then
- errors_info
- exit(1)
+ if not keep_going then
+ errors_info
+ exit(1)
+ end
+ return false
end
+ return true
end
# Display total error informations
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)
# * 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
- 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.
#
# 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
- 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
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
#
# Stops execution and prints errors if the program isn't available or didn't end correctly
fun exec_and_check(args: Array[String], error: String)
- do
- var prog = args.first
- args.remove_at 0
-
- # Is the wanted program available?
- var proc_which = new IProcess.from_a("which", [prog])
- proc_which.wait
- var res = proc_which.status
- if res != 0 then
- print "{error}: executable \"{prog}\" not found"
- exit 1
- end
-
- # Execute the wanted program
- var proc = new Process.from_a(prog, args)
- proc.wait
- res = proc.status
- if res != 0 then
- print "{error}: execution of \"{prog} {args.join(" ")}\" failed"
- exit 1
- end
- end
+ do
+ info("+ {args.join(" ")}", 2)
+
+ var prog = args.first
+ args.remove_at 0
+
+ # Is the wanted program available?
+ var proc_which = new ProcessReader.from_a("which", [prog])
+ proc_which.wait
+ var res = proc_which.status
+ if res != 0 then
+ print "{error}: executable \"{prog}\" not found"
+ exit 1
+ end
+
+ # Execute the wanted program
+ var proc = new Process.from_a(prog, args)
+ proc.wait
+ res = proc.status
+ if res != 0 then
+ print "{error}: execution of \"{prog} {args.join(" ")}\" failed"
+ exit 1
+ end
+ end
# Global OptionContext
var option_context = new OptionContext
# Option --log-dir
var opt_log_dir = new OptionString("Directory where to generate log files", "--log-dir")
+ # Option --nit-dir
+ var opt_nit_dir = new OptionString("Base directory of the Nit installation", "--nit-dir")
+
# Option --help
var opt_help = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
# 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")
# Option --bash-completion
var opt_bash_completion = new OptionBool("Generate bash_completion file for this program", "--bash-completion")
+ # Option --stub-man
+ var opt_stub_man = new OptionBool("Generate a stub manpage in pandoc markdown format", "--stub-man")
+
# Verbose level
var verbose_level: Int = 0
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_help, opt_version, opt_set_dummy_tool, opt_verbose, opt_bash_completion)
+ 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
+ opt_bash_completion.hidden = true
+ opt_set_dummy_tool.hidden = true
end
# Name, usage and synopsis of the tool.
exit 0
end
+ if opt_stub_man.value then
+ print """
+% {{{toolname.to_upper}}}(1)
+
+# NAME
+
+{{{tooldescription.split("\n")[1]}}}
+
+# SYNOPSYS
+
+{{{toolname}}} [*options*]...
+
+# OPTIONS
+"""
+ for o in option_context.options do
+ var first = true
+ for n in o.names do
+ if first then first = false else printn ", "
+ printn "`{n}`"
+ end
+ print ""
+ print ": {o.helptext}"
+ print ""
+ end
+ print """
+# SEE ALSO
+
+The Nit language documentation and the source code of its tools and libraries may be downloaded from <http://nitlanguage.org>"""
+ exit 0
+ end
+
var errors = option_context.get_errors
if not errors.is_empty then
for e in errors do print "Error: {e}"
exit 1
end
+ nit_dir = compute_nit_dir
+
if option_context.rest.is_empty and not accept_no_arguments then
print tooldescription
print "Use --help for help"
# 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
- nit_dir = compute_nit_dir
+ # 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.
end
# The identified root directory of the Nit project
- var nit_dir: nullable String = null
+ var nit_dir: String is noinit
- private fun compute_nit_dir: nullable String
+ private fun compute_nit_dir: String
do
- # a environ variable has precedence
- var res = "NIT_DIR".environ
- if not res.is_empty then return res
+ # the option has precedence
+ 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}.")
+ end
+ return res
+ end
+
+ # then the environ variable has precedence
+ 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}.")
+ end
+ return res
+ end
# find the runpath of the program from argv[0]
res = "{sys.program_name.dirname}/.."
- if res.file_exists and "{res}/src/nit.nit".file_exists then return res.simplify_path
+ if check_nit_dir(res) then return res.simplify_path
# find the runpath of the process from /proc
var exe = "/proc/self/exe"
if exe.file_exists then
res = exe.realpath
res = res.dirname.join_path("..")
- if res.file_exists and "{res}/src/nit.nit".file_exists then return res.simplify_path
+ if check_nit_dir(res) then return res.simplify_path
end
- return null
+ # search in the PATH
+ var ps = "PATH".environ.split(":")
+ for p in ps do
+ res = p/".."
+ 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.")
+ abort
+ end
+
+ private fun check_nit_dir(res: String): Bool
+ do
+ return res.file_exists and "{res}/src/nit.nit".file_exists
end
end
var toolcontext: ToolContext
- init(toolcontext: ToolContext) do
- self.toolcontext = toolcontext
- end
-
private fun extract_options_names: Array[String] do
var names = new Array[String]
for option in toolcontext.option_context.options do
addn " COMPREPLY=()"
addn " cur=\"$\{COMP_WORDS[COMP_CWORD]\}\""
addn " prev=\"$\{COMP_WORDS[COMP_CWORD-1]\}\""
- if option_names != null then
+ if not option_names.is_empty then
addn " opts=\"{option_names.join(" ")}\""
addn " if [[ $\{cur\} == -* ]] ; then"
addn " COMPREPLY=( $(compgen -W \"$\{opts\}\" -- $\{cur\}) )"