lib/standard: Stdin/out/err now part of Sys.
[nit.git] / src / toolcontext.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2006-2008 Floréal Morandat <morandat@lirmm.fr>
4 # Copyright 2008-2012 Jean Privat <jean@pryen.org>
5 # Copyright 2009 Jean-Sebastien Gelinas <calestar@gmail.com>
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18
19 # Common command-line tool infractructure than handle options and error messages
20 module toolcontext
21
22 import opts
23 import location
24 import version
25
26 class Message
27 super Comparable
28 redef type OTHER: Message
29
30 var location: nullable Location
31 var text: String
32
33 # Comparisons are made on message locations.
34 redef fun <(other: OTHER): Bool do
35 if location == null then return true
36 if other.location == null then return false
37
38 return location.as(not null) < other.location.as(not null)
39 end
40
41 redef fun to_s: String
42 do
43 var l = location
44 if l == null then
45 return text
46 else
47 return "{l}: {text}"
48 end
49 end
50
51 fun to_color_string: String
52 do
53 var esc = 27.ascii
54 var red = "{esc}[0;31m"
55 var bred = "{esc}[1;31m"
56 var green = "{esc}[0;32m"
57 var yellow = "{esc}[0;33m"
58 var def = "{esc}[0m"
59
60 var l = location
61 if l == null then
62 return text
63 else if l.file == null then
64 return "{yellow}{l}{def}: {text}"
65 else
66 return "{yellow}{l}{def}: {text}\n{l.colored_line("1;31")}"
67 end
68 end
69 end
70
71 # Global context for tools
72 class ToolContext
73 # Number of errors
74 var error_count: Int = 0
75
76 # Number of warnings
77 var warning_count: Int = 0
78
79 # Directory where to generate log files
80 var log_directory: String = "logs"
81
82 # Messages
83 private var messages: Array[Message] = new Array[Message]
84 private var message_sorter: ComparableSorter[Message] = new ComparableSorter[Message]
85
86 fun check_errors
87 do
88 if messages.length > 0 then
89 message_sorter.sort(messages)
90
91 for m in messages do
92 if opt_no_color.value then
93 sys.stderr.write("{m}\n")
94 else
95 sys.stderr.write("{m.to_color_string}\n")
96 end
97 end
98
99 messages.clear
100 end
101
102 if error_count > 0 then exit(1)
103 end
104
105 # Display an error
106 fun error(l: nullable Location, s: String)
107 do
108 messages.add(new Message(l,s))
109 error_count = error_count + 1
110 if opt_stop_on_first_error.value then check_errors
111 end
112
113 # Add an error, show errors and quit
114 fun fatal_error(l: nullable Location, s: String)
115 do
116 error(l,s)
117 check_errors
118 end
119
120 # Display a warning
121 fun warning(l: nullable Location, s: String)
122 do
123 if opt_warn.value == 0 then return
124 messages.add(new Message(l,s))
125 warning_count = warning_count + 1
126 if opt_stop_on_first_error.value then check_errors
127 end
128
129 # Display an info
130 fun info(s: String, level: Int)
131 do
132 if level <= verbose_level then
133 print "{s}"
134 end
135 end
136
137 # Global OptionContext
138 var option_context: OptionContext = new OptionContext
139
140 # Option --warn
141 var opt_warn: OptionCount = new OptionCount("Show warnings", "-W", "--warn")
142
143 # Option --quiet
144 var opt_quiet: OptionBool = new OptionBool("Do not show warnings", "-q", "--quiet")
145
146 # Option --log
147 var opt_log: OptionBool = new OptionBool("Generate various log files", "--log")
148
149 # Option --log-dir
150 var opt_log_dir: OptionString = new OptionString("Directory where to generate log files", "--log-dir")
151
152 # Option --help
153 var opt_help: OptionBool = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
154
155 # Option --version
156 var opt_version: OptionBool = new OptionBool("Show version and exit", "--version")
157
158 # Option --verbose
159 var opt_verbose: OptionCount = new OptionCount("Verbose", "-v", "--verbose")
160
161 # Option --stop-on-first-error
162 var opt_stop_on_first_error: OptionBool = new OptionBool("Stop on first error", "--stop-on-first-error")
163
164 # Option --no-color
165 var opt_no_color: OptionBool = new OptionBool("Do not use color to display errors and warnings", "--no-color")
166
167 # Verbose level
168 var verbose_level: Int = 0
169
170 init
171 do
172 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)
173 end
174
175 # Name, usage and synopsis of the tool.
176 # It is mainly used in `usage`.
177 # Should be correctly set by the client before calling `process_options`
178 # A multi-line string is recommmended.
179 #
180 # eg. `"Usage: tool [OPTION]... [FILE]...\nDo some things."`
181 var tooldescription: String writable = "Usage: [OPTION]... [ARG]..."
182
183 # Does `process_options` should accept an empty sequence of arguments.
184 # ie. nothing except options.
185 # Is `false` by default.
186 #
187 # If required, if should be set by the client before calling `process_options`
188 var accept_no_arguments writable = false
189
190 # print the full usage of the tool.
191 # Is called by `process_option` on `--help`.
192 # It also could be called by the client.
193 fun usage
194 do
195 print tooldescription
196 option_context.usage
197 end
198
199 # Parse and process the options given on the command line
200 fun process_options(args: Sequence[String])
201 do
202 self.opt_warn.value = 1
203
204 # init options
205 option_context.parse(args)
206
207 if opt_help.value then
208 usage
209 exit 0
210 end
211
212 if opt_version.value then
213 print nit_version
214 exit 0
215 end
216
217 var errors = option_context.get_errors
218 if not errors.is_empty then
219 for e in errors do print "Error: {e}"
220 print tooldescription
221 print "Use --help for help"
222 exit 1
223 end
224
225 if option_context.rest.is_empty and not accept_no_arguments then
226 print tooldescription
227 print "Use --help for help"
228 exit 1
229 end
230
231 # Set verbose level
232 verbose_level = opt_verbose.value
233
234 if self.opt_quiet.value then self.opt_warn.value = 0
235
236 if opt_log_dir.value != null then log_directory = opt_log_dir.value.as(not null)
237 if opt_log.value then
238 # Make sure the output directory exists
239 log_directory.mkdir
240 end
241
242 nit_dir = compute_nit_dir
243 end
244
245 # The identified root directory of the Nit project
246 var nit_dir: nullable String
247
248 private fun compute_nit_dir: nullable String
249 do
250 # a environ variable has precedence
251 var res = "NIT_DIR".environ
252 if not res.is_empty then return res
253
254 # find the runpath of the program from argv[0]
255 res = "{sys.program_name.dirname}/.."
256 if res.file_exists and "{res}/src/nit.nit".file_exists then return res.simplify_path
257
258 # find the runpath of the process from /proc
259 var exe = "/proc/self/exe"
260 if exe.file_exists then
261 res = exe.realpath
262 res = res.dirname.join_path("..")
263 if res.file_exists and "{res}/src/nit.nit".file_exists then return res.simplify_path
264 end
265
266 return null
267 end
268 end