Merge: nitg: Added PNaCl support for Nit
[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 # Executes a program while checking if it's available and if the execution ended correctly
138 #
139 # Stops execution and prints errors if the program isn't available or didn't end correctly
140 fun exec_and_check(args: Array[String], error: String)
141 do
142 var prog = args.first
143 args.remove_at 0
144
145 # Is the wanted program available?
146 var proc_which = new IProcess.from_a("which", [prog])
147 proc_which.wait
148 var res = proc_which.status
149 if res != 0 then
150 print "{error}: executable \"{prog}\" not found"
151 exit 1
152 end
153
154 # Execute the wanted program
155 var proc = new Process.from_a(prog, args)
156 proc.wait
157 res = proc.status
158 if res != 0 then
159 print "{error}: execution of \"{prog} {args.join(" ")}\" failed"
160 exit 1
161 end
162 end
163
164 # Global OptionContext
165 var option_context: OptionContext = new OptionContext
166
167 # Option --warn
168 var opt_warn: OptionCount = new OptionCount("Show warnings", "-W", "--warn")
169
170 # Option --quiet
171 var opt_quiet: OptionBool = new OptionBool("Do not show warnings", "-q", "--quiet")
172
173 # Option --log
174 var opt_log: OptionBool = new OptionBool("Generate various log files", "--log")
175
176 # Option --log-dir
177 var opt_log_dir: OptionString = new OptionString("Directory where to generate log files", "--log-dir")
178
179 # Option --help
180 var opt_help: OptionBool = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
181
182 # Option --version
183 var opt_version: OptionBool = new OptionBool("Show version and exit", "--version")
184
185 # Option --verbose
186 var opt_verbose: OptionCount = new OptionCount("Verbose", "-v", "--verbose")
187
188 # Option --stop-on-first-error
189 var opt_stop_on_first_error: OptionBool = new OptionBool("Stop on first error", "--stop-on-first-error")
190
191 # Option --no-color
192 var opt_no_color: OptionBool = new OptionBool("Do not use color to display errors and warnings", "--no-color")
193
194 # Verbose level
195 var verbose_level: Int = 0
196
197 init
198 do
199 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)
200 end
201
202 # Name, usage and synopsis of the tool.
203 # It is mainly used in `usage`.
204 # Should be correctly set by the client before calling `process_options`
205 # A multi-line string is recommmended.
206 #
207 # eg. `"Usage: tool [OPTION]... [FILE]...\nDo some things."`
208 var tooldescription: String writable = "Usage: [OPTION]... [ARG]..."
209
210 # Does `process_options` should accept an empty sequence of arguments.
211 # ie. nothing except options.
212 # Is `false` by default.
213 #
214 # If required, if should be set by the client before calling `process_options`
215 var accept_no_arguments writable = false
216
217 # print the full usage of the tool.
218 # Is called by `process_option` on `--help`.
219 # It also could be called by the client.
220 fun usage
221 do
222 print tooldescription
223 option_context.usage
224 end
225
226 # Parse and process the options given on the command line
227 fun process_options(args: Sequence[String])
228 do
229 self.opt_warn.value = 1
230
231 # init options
232 option_context.parse(args)
233
234 if opt_help.value then
235 usage
236 exit 0
237 end
238
239 if opt_version.value then
240 print nit_version
241 exit 0
242 end
243
244 var errors = option_context.get_errors
245 if not errors.is_empty then
246 for e in errors do print "Error: {e}"
247 print tooldescription
248 print "Use --help for help"
249 exit 1
250 end
251
252 if option_context.rest.is_empty and not accept_no_arguments then
253 print tooldescription
254 print "Use --help for help"
255 exit 1
256 end
257
258 # Set verbose level
259 verbose_level = opt_verbose.value
260
261 if self.opt_quiet.value then self.opt_warn.value = 0
262
263 if opt_log_dir.value != null then log_directory = opt_log_dir.value.as(not null)
264 if opt_log.value then
265 # Make sure the output directory exists
266 log_directory.mkdir
267 end
268
269 nit_dir = compute_nit_dir
270 end
271
272 # The identified root directory of the Nit project
273 var nit_dir: nullable String
274
275 private fun compute_nit_dir: nullable String
276 do
277 # a environ variable has precedence
278 var res = "NIT_DIR".environ
279 if not res.is_empty then return res
280
281 # find the runpath of the program from argv[0]
282 res = "{sys.program_name.dirname}/.."
283 if res.file_exists and "{res}/src/nit.nit".file_exists then return res.simplify_path
284
285 # find the runpath of the process from /proc
286 var exe = "/proc/self/exe"
287 if exe.file_exists then
288 res = exe.realpath
289 res = res.dirname.join_path("..")
290 if res.file_exists and "{res}/src/nit.nit".file_exists then return res.simplify_path
291 end
292
293 return null
294 end
295 end