# limitations under the License.
# Common command-line tool infractructure than handle options and error messages
-package toolcontext
+module toolcontext
import opts
import location
+import version
class Message
super Comparable
redef type OTHER: Message
- readable var _location: nullable Location
- readable var _text: String
+ var location: nullable Location
+ var text: String
# Comparisons are made on message locations.
redef fun <(other: OTHER): Bool do
# Global context for tools
class ToolContext
# Number of errors
- readable var _error_count: Int = 0
+ var error_count: Int = 0
# Number of warnings
- readable var _warning_count: Int = 0
+ var warning_count: Int = 0
# Directory where to generate log files
- readable var _log_directory: String = "logs"
+ var log_directory: String = "logs"
# Messages
- var _messages: Array[Message] = new Array[Message]
- var _message_sorter: ComparableSorter[Message] = new ComparableSorter[Message]
+ private var messages: Array[Message] = new Array[Message]
+ private var message_sorter: ComparableSorter[Message] = new ComparableSorter[Message]
fun check_errors
do
- if _messages.length > 0 then
- _message_sorter.sort(_messages)
+ if messages.length > 0 then
+ message_sorter.sort(messages)
- for m in _messages do
+ for m in messages do
if opt_no_color.value then
- stderr.write("{m}\n")
+ sys.stderr.write("{m}\n")
else
- stderr.write("{m.to_color_string}\n")
+ sys.stderr.write("{m.to_color_string}\n")
end
end
- _messages.clear
+ messages.clear
end
if error_count > 0 then exit(1)
# Display an error
fun error(l: nullable Location, s: String)
do
- _messages.add(new Message(l,s))
- _error_count = _error_count + 1
+ messages.add(new Message(l,s))
+ error_count = error_count + 1
if opt_stop_on_first_error.value then check_errors
end
# Display a warning
fun warning(l: nullable Location, s: String)
do
- if _opt_warn.value == 0 then return
- _messages.add(new Message(l,s))
- _warning_count = _warning_count + 1
+ if opt_warn.value == 0 then return
+ messages.add(new Message(l,s))
+ warning_count = warning_count + 1
if opt_stop_on_first_error.value then check_errors
end
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
+
# Global OptionContext
- readable var _option_context: OptionContext = new OptionContext
+ var option_context: OptionContext = new OptionContext
# Option --warn
- readable var _opt_warn: OptionCount = new OptionCount("Show warnings", "-W", "--warn")
+ var opt_warn: OptionCount = new OptionCount("Show warnings", "-W", "--warn")
# Option --quiet
- readable var _opt_quiet: OptionBool = new OptionBool("Do not show warnings", "-q", "--quiet")
+ var opt_quiet: OptionBool = new OptionBool("Do not show warnings", "-q", "--quiet")
# Option --log
- readable var _opt_log: OptionBool = new OptionBool("Generate various log files", "--log")
+ var opt_log: OptionBool = new OptionBool("Generate various log files", "--log")
# Option --log-dir
- readable var _opt_log_dir: OptionString = new OptionString("Directory where to generate log files", "--log-dir")
+ var opt_log_dir: OptionString = new OptionString("Directory where to generate log files", "--log-dir")
# Option --help
- readable var _opt_help: OptionBool = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
+ var opt_help: OptionBool = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
# Option --version
- readable var _opt_version: OptionBool = new OptionBool("Show version and exit", "--version")
+ var opt_version: OptionBool = new OptionBool("Show version and exit", "--version")
# Option --verbose
- readable var _opt_verbose: OptionCount = new OptionCount("Verbose", "-v", "--verbose")
+ var opt_verbose: OptionCount = new OptionCount("Verbose", "-v", "--verbose")
# Option --stop-on-first-error
- readable var _opt_stop_on_first_error: OptionBool = new OptionBool("Stop on first error", "--stop-on-first-error")
+ var opt_stop_on_first_error: OptionBool = new OptionBool("Stop on first error", "--stop-on-first-error")
# Option --no-color
- readable var _opt_no_color: OptionBool = new OptionBool("Do not use color to display errors and warnings", "--no-color")
+ var opt_no_color: OptionBool = new OptionBool("Do not use color to display errors and warnings", "--no-color")
# Verbose level
- readable var _verbose_level: Int = 0
+ var verbose_level: Int = 0
init
do
option_context.add_option(opt_warn, opt_quiet, opt_stop_on_first_error, opt_no_color, opt_log, opt_log_dir, opt_help, opt_version, opt_verbose)
end
+ # Name, usage and synopsis of the tool.
+ # It is mainly used in `usage`.
+ # Should be correctly set by the client before calling `process_options`
+ # A multi-line string is recommmended.
+ #
+ # eg. `"Usage: tool [OPTION]... [FILE]...\nDo some things."`
+ var tooldescription: String writable = "Usage: [OPTION]... [ARG]..."
+
+ # Does `process_options` should accept an empty sequence of arguments.
+ # ie. nothing except options.
+ # Is `false` by default.
+ #
+ # If required, if should be set by the client before calling `process_options`
+ var accept_no_arguments writable = false
+
+ # print the full usage of the tool.
+ # Is called by `process_option` on `--help`.
+ # It also could be called by the client.
+ fun usage
+ do
+ print tooldescription
+ option_context.usage
+ end
+
# Parse and process the options given on the command line
- fun process_options
+ fun process_options(args: Sequence[String])
do
self.opt_warn.value = 1
# init options
option_context.parse(args)
+ if opt_help.value then
+ usage
+ exit 0
+ end
+
+ if opt_version.value then
+ print nit_version
+ exit 0
+ end
+
+ var errors = option_context.get_errors
+ if not errors.is_empty then
+ for e in errors do print "Error: {e}"
+ print tooldescription
+ print "Use --help for help"
+ exit 1
+ end
+
+ if option_context.rest.is_empty and not accept_no_arguments then
+ print tooldescription
+ print "Use --help for help"
+ exit 1
+ end
+
# Set verbose level
- _verbose_level = opt_verbose.value
+ verbose_level = opt_verbose.value
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
+ 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
+ end
+
+ # The identified root directory of the Nit project
+ var nit_dir: nullable String
+
+ private fun compute_nit_dir: nullable String
+ do
+ # a environ variable has precedence
+ var res = "NIT_DIR".environ
+ if not res.is_empty then return res
+
+ # 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
+
+ # 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
+ end
+
+ return null
end
end