ni: extracted model facilities in their own module
[nit.git] / src / ni.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2008 Jean Privat <jean@pryen.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 module ni
18
19 import model_utils
20
21 private class Pager
22 var content: String = ""
23 fun add(text: String) do addn("{text}\n")
24 fun addn(text: String) do content += text.escape
25 fun add_rule do add("\n---\n")
26 fun render do sys.system("echo \"{content}\" | pager -r")
27 end
28
29 class NitIndex
30 private var toolcontext: ToolContext
31 private var model: Model
32 private var mbuilder: ModelBuilder
33 private var mainmodule: MModule
34 private var arguments: Array[String]
35
36 init(toolcontext: ToolContext) do
37 # We need a model to collect stufs
38 self.toolcontext = toolcontext
39 self.arguments = toolcontext.option_context.rest
40
41 if arguments.length > 2 then
42 print "usage: ni path/to/module.nit [expression]"
43 toolcontext.option_context.usage
44 exit(1)
45 end
46
47 model = new Model
48 mbuilder = new ModelBuilder(model, toolcontext)
49
50 # Here we load an process std modules
51 #var dir = "NIT_DIR".environ
52 #var mmodules = modelbuilder.parse_and_build(["{dir}/lib/standard/standard.nit"])
53 var mmodules = mbuilder.parse_and_build([arguments.first])
54 if mmodules.is_empty then return
55 mbuilder.full_propdef_semantic_analysis
56 assert mmodules.length == 1
57 self.mainmodule = mmodules.first
58 end
59
60 fun start do
61 if arguments.length == 1 then
62 welcome
63 prompt
64 else
65 seek(arguments[1])
66 end
67 end
68
69 fun welcome do
70 print "Welcome in Nit Index.\n"
71 print "Loaded modules"
72 for m in mbuilder.nmodules do
73 print " - {m.mmodule.name}"
74 end
75 print "\nEnter the module, class or property name you want to look up."
76 print "Enter a blank line to exit.\n"
77 end
78
79 fun prompt do
80 printn ">> "
81 seek(stdin.read_line)
82 end
83
84 fun seek(entry: String) do
85 # quitting?
86 if entry.is_empty then exit(0)
87
88 var flag = false
89 for n in mbuilder.nmodules do
90 var m = n.mmodule
91 if m.name == entry then
92 flag = true
93 module_fulldoc(n)
94 end
95 end
96 for c in model.mclasses do
97 if c.name == entry then
98 if not mbuilder.mclassdef2nclassdef[c.intro] isa AStdClassdef then continue
99 flag = true
100 doc_class(c)
101 end
102 end
103 var matches = new List[MProperty]
104 for p in model.mproperties do
105 if p.name == entry then
106 flag = true
107 matches.add(p)
108 end
109 end
110 #if not matches.is_empty then doc_properties
111
112 if not flag then print "Nothing known about '{entry}'"
113 if arguments.length == 1 then prompt
114 end
115
116 private fun module_fulldoc(nmodule: AModule) do
117 var mmodule = nmodule.mmodule
118 var pager = new Pager
119 pager.add("# module {mmodule.name}\n".bold)
120 pager.add("import {mmodule.in_importation.direct_greaters.join(", ")}")
121 pager.add_rule
122 pager.addn(nmodule.comment.green)
123 pager.add_rule
124
125 var cats = new HashMap[String, Collection[MClass]]
126 cats["introduced classes"] = mmodule.intro_mclasses
127 cats["refined classes"] = mmodule.redef_mclasses
128 cats["inherited classes"] = mmodule.imported_mclasses
129
130 for cat, list in cats do
131 if not list.is_empty then
132 pager.add("# {cat}\n".bold)
133 for mclass in list do
134 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro].as(AStdClassdef)
135 if not nclass.short_comment.is_empty then
136 pager.add("\t# {nclass.short_comment}")
137 end
138 if cat == "refined classes" then
139 pager.add("\tredef {mclass.short_doc}")
140 else
141 pager.add("\t{mclass.short_doc}")
142 end
143 if not mclass.intro_mmodule == mmodule then
144 pager.add("\t\tintroduced in {mmodule.full_name}::{mclass}".gray)
145 end
146 pager.add("")
147 end
148 end
149 end
150 pager.add_rule
151 pager.render
152 end
153
154 fun doc_class(mclass: MClass) do
155 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro].as(AStdClassdef)
156 var pager = new Pager
157 pager.add("# {mclass.intro_mmodule.public_owner.name}::{mclass.name}\n".bold)
158 pager.add("{mclass.short_doc} ")
159 pager.add_rule
160 pager.addn(nclass.comment.green)
161 pager.add_rule
162 #TODO VT
163 pager.add_rule
164
165 var cats = new HashMap[String, Collection[MMethod]]
166 cats["constructors"] = mclass.constructors
167 cats["introduced methods"] = mclass.intro_methods
168 cats["refined methods"] = mclass.redef_methods
169 cats["inherited methods"] = mclass.inherited_methods
170
171 for cat, list in cats do
172 if not list.is_empty then
173 pager.add("# {cat}\n".bold)
174 for mmethod in list do
175 #TODO verifier cast
176 var nmethod = mbuilder.mpropdef2npropdef[mmethod.intro].as(AMethPropdef)
177 if not nmethod.short_comment.is_empty then
178 pager.add("\t# {nmethod.short_comment}")
179 end
180 if cat == "refined methods" then
181 pager.add("\tredef {nmethod}")
182 else
183 pager.add("\t{nmethod}")
184 end
185 if not mmethod.intro_mclassdef == mclass.intro then
186 pager.add("\t\tintroduced in {mmethod.intro_mclassdef.namespace}::{mmethod}".gray)
187 end
188 pager.add("")
189 end
190 end
191 end
192 pager.render
193 end
194 end
195
196 # Printing facilities
197
198 redef class MClass
199
200 redef fun to_s: String do
201 if arity > 0 then
202 return "{name}[{intro.parameter_names.join(", ")}]"
203 else
204 return name
205 end
206 end
207
208 private fun short_doc: String do
209 var ret = ""
210 if is_interface then ret = "interface {ret}"
211 if is_enum then ret = "enum {ret}"
212 if is_class then ret = "class {ret}"
213 if is_abstract then ret = "abstract {ret}"
214 if visibility.to_s == "public" then ret = "{ret}{to_s.green}"
215 if visibility.to_s == "private" then ret = "{ret}{to_s.red}"
216 if visibility.to_s == "protected" then ret = "{ret}{to_s.yellow}"
217 ret = "{ret} super {parents.join(", ")}"
218 return ret
219 end
220 end
221
222 redef class MClassDef
223 private fun namespace: String do
224 return "{mmodule.full_name}::{mclass.name}"
225 end
226 end
227
228 redef class AModule
229 private fun comment: String do
230 var ret = ""
231 for t in n_moduledecl.n_doc.n_comment do
232 ret += "{t.text.replace("# ", "")}"
233 end
234 return ret
235 end
236 end
237
238 redef class AStdClassdef
239 private fun comment: String do
240 var ret = ""
241 if n_doc != null then
242 for t in n_doc.n_comment do
243 var txt = t.text.replace("# ", "")
244 txt = txt.replace("#", "")
245 ret += "{txt}"
246 end
247 end
248 return ret
249 end
250
251 private fun short_comment: String do
252 var ret = ""
253 if n_doc != null then
254 var txt = n_doc.n_comment.first.text
255 txt = txt.replace("# ", "")
256 txt = txt.replace("\n", "")
257 ret += txt
258 end
259 return ret
260 end
261 end
262
263 redef class AMethPropdef
264 private fun short_comment: String do
265 var ret = ""
266 if n_doc != null then
267 var txt = n_doc.n_comment.first.text
268 txt = txt.replace("# ", "")
269 txt = txt.replace("\n", "")
270 ret += txt
271 end
272 return ret
273 end
274
275 redef fun to_s do
276 var ret = ""
277 if not mpropdef.mproperty.is_init then
278 ret = "fun "
279 end
280 if mpropdef.mproperty.visibility.to_s == "public" then ret = "{ret}{mpropdef.mproperty.name.green}"
281 if mpropdef.mproperty.visibility.to_s == "private" then ret = "{ret}{mpropdef.mproperty.name.red}"
282 if mpropdef.mproperty.visibility.to_s == "protected" then ret = "{ret}{mpropdef.mproperty.name.yellow}"
283 if n_signature != null then ret = "{ret}{n_signature.to_s}"
284 if n_kwredef != null then ret = "redef {ret}"
285 if self isa ADeferredMethPropdef then ret = "{ret} is abstract"
286 if self isa AInternMethPropdef then ret = "{ret} is intern"
287 if self isa AExternMethPropdef then ret = "{ret} is extern"
288 return ret
289 end
290 end
291
292 redef class ASignature
293 redef fun to_s do
294 #TODO closures
295 var ret = ""
296 if not n_params.is_empty then
297 ret = "{ret}({n_params.join(", ")})"
298 end
299 if n_type != null then ret += ": {n_type.to_s}"
300 return ret
301 end
302 end
303
304 redef class AParam
305 redef fun to_s do
306 var ret = "{n_id.text}"
307 if n_type != null then
308 ret = "{ret}: {n_type.to_s}"
309 if n_dotdotdot != null then ret = "{ret}..."
310 end
311 return ret
312 end
313 end
314
315 redef class AType
316 redef fun to_s do
317 var ret = n_id.text
318 if n_kwnullable != null then ret = "nullable {ret}"
319 if not n_types.is_empty then ret = "{ret}[{n_types.join(", ")}]"
320 return ret
321 end
322 end
323
324 # Redef String class to add a function to color the string
325 redef class String
326
327 private fun add_escape_char(escapechar: String): String do
328 return "{escapechar}{self}\\033[0m"
329 end
330
331 private fun esc: Char do return 27.ascii
332 private fun red: String do return add_escape_char("{esc}[1;31m")
333 private fun yellow: String do return add_escape_char("{esc}[1;33m")
334 private fun green: String do return add_escape_char("{esc}[1;32m")
335 private fun blue: String do return add_escape_char("{esc}[1;34m")
336 private fun cyan: String do return add_escape_char("{esc}[1;36m")
337 private fun gray: String do return add_escape_char("{esc}[30;1m")
338 private fun bold: String do return add_escape_char("{esc}[1m")
339 private fun underline: String do return add_escape_char("{esc}[4m")
340
341 private fun escape: String
342 do
343 var b = new Buffer
344 for c in self do
345 if c == '\n' then
346 b.append("\\n")
347 else if c == '\0' then
348 b.append("\\0")
349 else if c == '"' then
350 b.append("\\\"")
351 else if c == '\\' then
352 b.append("\\\\")
353 else if c == '`' then
354 b.append("'")
355 else if c.ascii < 32 then
356 b.append("\\{c.ascii.to_base(8, false)}")
357 else
358 b.add(c)
359 end
360 end
361 return b.to_s
362 end
363 end
364
365 # Create a tool context to handle options and paths
366 var toolcontext = new ToolContext
367 toolcontext.process_options
368
369 # Here we launch the nit index
370 var ni = new NitIndex(toolcontext)
371 ni.start
372
373 # TODO seek methods
374 # TODO lister les methods qui retournent un certain type
375 # TODO lister les methods qui utilisent un certain type
376 # TODO lister les sous-types connus d'une type
377 # TODO sorter par ordre alphabétique