ni: better display of welcome screen
[nit.git] / src / ni.nit
index 1b9ecfe..03ffaa0 100644 (file)
@@ -1,7 +1,5 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
-# Copyright 2008 Jean Privat <jean@pryen.org>
-#
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# ni or nit index, is a command tool used to display documentation
 module ni
 
 import model_utils
 
 private class Pager
-       var content: String = ""
+       var content = new Buffer
        fun add(text: String) do addn("{text}\n")
-       fun addn(text: String) do content += text.escape
+       fun addn(text: String) do content.append(text.escape)
        fun add_rule do add("\n---\n")
        fun render do sys.system("echo \"{content}\" | pager -r")
 end
 
+# Main class of the nit index tool
+# NitIndex build the model using the toolcontext argument
+# then wait for query on std in to display documentation
 class NitIndex
        private var toolcontext: ToolContext
        private var model: Model
@@ -36,9 +38,10 @@ class NitIndex
        init(toolcontext: ToolContext) do
                # We need a model to collect stufs
                self.toolcontext = toolcontext
+               self.toolcontext.option_context.options.clear
                self.arguments = toolcontext.option_context.rest
 
-               if arguments.length > 2 then
+               if arguments.is_empty or arguments.length > 2 then
                        print "usage: ni path/to/module.nit [expression]"
                        toolcontext.option_context.usage
                        exit(1)
@@ -52,7 +55,6 @@ class NitIndex
                #var mmodules = modelbuilder.parse_and_build(["{dir}/lib/standard/standard.nit"])
                var mmodules = mbuilder.parse_and_build([arguments.first])
                if mmodules.is_empty then return
-               mbuilder.full_propdef_semantic_analysis
                assert mmodules.length == 1
                self.mainmodule = mmodules.first
        end
@@ -67,13 +69,20 @@ class NitIndex
        end
 
        fun welcome do
-               print "Welcome in Nit Index.\n"
-               print "Loaded modules"
-               for m in mbuilder.nmodules do
-                       print " - {m.mmodule.name}"
+               print "Welcome in the Nit Index."
+               print "\nCommands:"
+               print "\tname\t\tlookup module, class and property with the corresponding 'name'"
+               print "\tparam: Type\tlookup methods using the corresponding 'Type' as parameter"
+               print "\treturn: Type\tlookup methods returning the corresponding 'Type'"
+               print "\tEnter a blank line to exit.\n"
+               print "\nLoaded modules:"
+               var mmodules = new Array[MModule]
+               mmodules.add_all(model.mmodules)
+               var sorter = new MModuleNameSorter
+               sorter.sort(mmodules)
+               for m in mmodules do
+                       print "\t{m.name}"
                end
-               print "\nEnter the module, class or property name you want to look up."
-               print "Enter a blank line to exit.\n"
        end
 
        fun prompt do
@@ -84,33 +93,50 @@ class NitIndex
        fun seek(entry: String) do
                if entry.is_empty then exit(0)
                var flag = false
-               # seek for modules
-               var mmatches = new List[MModule]
-               for m in model.mmodules do
-                       if m.name == entry then
+               # seek return types
+               if entry.has_prefix("return:") then
+                       var ret = entry.split_with(":")[1].replace(" ", "")
+                       var matches = seek_returns(ret)
+                       if not matches.is_empty then
                                flag = true
-                               mmatches.add(m)
+                               props_fulldoc(matches)
                        end
-               end
-               if not mmatches.is_empty then modules_fulldoc(mmatches)
-               # seek for classes
-               var cmatches = new List[MClass]
-               for c in model.mclasses do
-                       if c.name == entry then
+               else if entry.has_prefix("param:") then
+                       var param = entry.split_with(":")[1].replace(" ", "")
+                       var matches = seek_params(param)
+                       if not matches.is_empty then
                                flag = true
-                               cmatches.add(c)
+                               props_fulldoc(matches)
                        end
-               end
-               if not cmatches.is_empty then classes_fulldoc(cmatches)
-               # seek for properties
-               var matches = new List[MProperty]
-               for p in model.mproperties do
-                       if p.name == entry then
-                               flag = true
-                               matches.add(p)
+               else
+                       # seek for modules
+                       var mmatches = new List[MModule]
+                       for m in model.mmodules do
+                               if m.name == entry then
+                                       flag = true
+                                       mmatches.add(m)
+                               end
+                       end
+                       if not mmatches.is_empty then modules_fulldoc(mmatches)
+                       # seek for classes
+                       var cmatches = new List[MClass]
+                       for c in model.mclasses do
+                               if c.name == entry then
+                                       flag = true
+                                       cmatches.add(c)
+                               end
+                       end
+                       if not cmatches.is_empty then classes_fulldoc(cmatches)
+                       # seek for properties
+                       var matches = new List[MProperty]
+                       for p in model.mproperties do
+                               if p.name == entry then
+                                       flag = true
+                                       matches.add(p)
+                               end
                        end
+                       if not matches.is_empty then props_fulldoc(matches)
                end
-               if not matches.is_empty then props_fulldoc(matches)
                # no matches
                if not flag then print "Nothing known about '{entry}'"
                if arguments.length == 1 then prompt
@@ -128,29 +154,34 @@ class NitIndex
                                pager.add("known clients: ".bold + "{mmodule.in_importation.direct_smallers.join(", ")}\n")
                        end
                        pager.add_rule
-                       pager.addn(nmodule.comment.green)
+                       pager.addn(nmodule.n_moduledecl.n_doc.comment.green)
                        pager.add_rule
 
                        var cats = new HashMap[String, Collection[MClass]]
                        cats["introduced classes"] = mmodule.intro_mclasses
                        cats["refined classes"] = mmodule.redef_mclasses
-                       cats["inherited classes"] = mmodule.imported_mclasses
+                       cats["imported classes"] = mmodule.imported_mclasses
 
                        for cat, list in cats do
                                if not list.is_empty then
-                                       pager.add("# {cat}".bold)
-                                       for mclass in list do
+                                       pager.add("\n# {cat}".bold)
+                                       #sort list
+                                       var sorted = new Array[MClass]
+                                       sorted.add_all(list)
+                                       var sorter = new MClassNameSorter
+                                       sorter.sort(sorted)
+                                       for mclass in sorted do
                                                var nclass = mbuilder.mclassdef2nclassdef[mclass.intro].as(AStdClassdef)
                                                pager.add("")
-                                               if not nclass.short_comment.is_empty then
-                                                       pager.add("\t# {nclass.short_comment}")
+                                               if not nclass.n_doc == null and not nclass.n_doc.short_comment.is_empty then
+                                                       pager.add("\t# {nclass.n_doc.short_comment}")
                                                end
                                                if cat == "refined classes" then
                                                        pager.add("\tredef {mclass.short_doc}")
                                                else
                                                        pager.add("\t{mclass.short_doc}")
                                                end
-                                               if not mclass.intro_mmodule == mmodule then
+                                               if cat != "introduced classes" then
                                                        pager.add("\t\t" + "introduced in {mmodule.full_name}::{mclass}".gray)
                                                end
                                                for mclassdef in mclass.mclassdefs do
@@ -173,8 +204,10 @@ class NitIndex
 
                        pager.add("# {mclass.namespace}\n".bold)
                        pager.add("{mclass.short_doc}")
-                       pager.add_rule
-                       pager.addn(nclass.comment.green)
+                       if not nclass.n_doc == null then
+                               pager.add_rule
+                               pager.addn(nclass.n_doc.comment.green)
+                       end
                        pager.add_rule
                        if not mclass.parameter_types.is_empty then
                                pager.add("# formal types".bold)
@@ -187,7 +220,7 @@ class NitIndex
                                pager.add("# virtual types".bold)
                                for vt in mclass.virtual_types do
                                        pager.add("")
-                                       vt_fulldoc(pager, vt)
+                                       mpropdef_fulldoc(pager, vt.intro)
                                end
                        end
                        pager.add_rule
@@ -200,10 +233,15 @@ class NitIndex
 
                        for cat, list in cats do
                                if not list.is_empty then
+                                       #sort list
+                                       var sorted = new Array[MMethod]
+                                       sorted.add_all(list)
+                                       var sorter = new MPropertyNameSorter
+                                       sorter.sort(sorted)
                                        pager.add("\n# {cat}".bold)
-                                       for mprop in list do
+                                       for mprop in sorted do
                                                pager.add("")
-                                               method_fulldoc(pager, mprop)
+                                               mpropdef_fulldoc(pager, mprop.intro)
                                        end
                                end
                        end
@@ -212,45 +250,80 @@ class NitIndex
                pager.render
        end
 
-       private fun props_fulldoc(mprops: List[MProperty]) do
+       private fun props_fulldoc(raw_mprops: List[MProperty]) do
                var pager = new Pager
-               # TODO group by module
-               for mprop in mprops do
-                       if mprop isa MMethod and mbuilder.mpropdef2npropdef.has_key(mprop.intro) then
-                               method_fulldoc(pager, mprop)
-                               pager.add_rule
-                       else if mprop isa MVirtualTypeProp then
-                               vt_fulldoc(pager, mprop)
-                               pager.add_rule
+               # group by module
+               var cats = new HashMap[MClass, Array[MProperty]]
+               for mprop in raw_mprops do
+                       if not mbuilder.mpropdef2npropdef.has_key(mprop.intro) then continue
+                       if mprop isa MAttribute then continue
+                       var mclass = mprop.intro_mclassdef.mclass
+                       if not cats.has_key(mclass) then cats[mclass] = new Array[MProperty]
+                       cats[mclass].add(mprop)
+               end
+               #sort groups
+               var sorter = new MClassNameSorter
+               var sorted = new Array[MClass]
+               sorted.add_all(cats.keys)
+               sorter.sort(sorted)
+               # display
+               for mclass in sorted do
+                       var mprops = cats[mclass]
+                       pager.add("# {mclass.namespace}".bold)
+                       var sorterp = new MPropertyNameSorter
+                       sorterp.sort(mprops)
+                       for mprop in mprops do
+                               pager.add("")
+                               mpropdef_fulldoc(pager, mprop.intro)
                        end
+                       pager.add_rule
                end
                pager.render
        end
 
-       private fun method_fulldoc(pager: Pager, mmethod: MMethod) do
-               if mbuilder.mpropdef2npropdef.has_key(mmethod.intro) then
-                       var nmethod = mbuilder.mpropdef2npropdef[mmethod.intro]
-                       if nmethod isa AMethPropdef then
-                               if not nmethod.short_comment.is_empty then
-                                       pager.add("\t# {nmethod.short_comment}")
-                               end
-                               pager.add("\t{nmethod}")
-                               pager.add("\t\t" + "introduced in {mmethod.intro_mclassdef.namespace}".gray)
-                               for mpropdef in mmethod.mpropdefs do
-                                       if mpropdef != mmethod.intro then
-                                               pager.add("\t\t" + "refined in {mpropdef.mclassdef.namespace}".gray)
-                                       end
+       private fun seek_returns(entry: String): List[MProperty] do
+               # TODO how to match with generic types?
+               var matches = new List[MProperty]
+               for mprop in model.mproperties do
+                       var intro = mprop.intro
+                       if intro isa MMethodDef then
+                               if intro.msignature.return_mtype != null and intro.msignature.return_mtype.to_s == entry then matches.add(mprop)
+                       else if intro isa MAttributeDef then
+                               if intro.static_mtype.to_s == entry then matches.add(mprop)
+                       end
+               end
+               return matches
+       end
+
+       private fun seek_params(entry: String): List[MProperty] do
+               # TODO how to match with generic types?
+               var matches = new List[MProperty]
+               for mprop in model.mproperties do
+                       var intro = mprop.intro
+                       if intro isa MMethodDef then
+                               var mparameters = intro.msignature.mparameters
+                               for mparameter in mparameters do
+                                       if mparameter.mtype.to_s == entry then matches.add(mprop)
                                end
+                       else if intro isa MAttributeDef then
+                               if intro.static_mtype.to_s == entry then matches.add(mprop)
                        end
                end
+               return matches
        end
 
-       private fun vt_fulldoc(pager: Pager, vt: MVirtualTypeProp) do
-               pager.add("\t{vt.short_doc}")
-               pager.add("\t\t" + "introduced in {vt.intro_mclassdef.namespace}::{vt}".gray)
-               for mpropdef in vt.mpropdefs do
-                       if mpropdef != vt.intro then
-                               pager.add("\t\t" + "refined in {mpropdef.mclassdef.namespace}".gray)
+       private fun mpropdef_fulldoc(pager: Pager, mpropdef: MPropDef) do
+               if mbuilder.mpropdef2npropdef.has_key(mpropdef) then
+                       var nprop = mbuilder.mpropdef2npropdef[mpropdef]
+                       if not nprop.n_doc == null and not nprop.n_doc.short_comment.is_empty then
+                               pager.add("\t# {nprop.n_doc.short_comment}")
+                       end
+               end
+               pager.add("\t{mpropdef}")
+               pager.add("\t\t" + "introduced in {mpropdef.mproperty.intro_mclassdef.namespace}".gray)
+               for mpdef in mpropdef.mproperty.mpropdefs do
+                       if not mpdef.is_intro then
+                               pager.add("\t\t" + "refined in {mpdef.mclassdef.namespace}".gray)
                        end
                end
        end
@@ -265,7 +338,6 @@ redef class MModule
 end
 
 redef class MClass
-
        redef fun to_s: String do
                if arity > 0 then
                        return "{name}[{intro.parameter_names.join(", ")}]"
@@ -290,7 +362,11 @@ redef class MClass
        end
 
        private fun namespace: String do
-               return "{intro_mmodule.public_owner.name}::{name}"
+               if not intro_mmodule.public_owner == null then
+                       return "{intro_mmodule.public_owner.name}::{name}"
+               else
+                       return "{intro_mmodule.name}::{name}"
+               end
        end
 end
 
@@ -300,108 +376,125 @@ redef class MClassDef
        end
 end
 
-redef class MVirtualTypeProp
-       private fun short_doc: String do
-               var ret = ""
-               if visibility.to_s == "public" then ret = "{to_s.green}: {intro.bound.to_s}"
-               if visibility.to_s == "private" then ret = "\t{to_s.red}: {intro.bound.to_s}"
-               if visibility.to_s == "protected" then ret = "\t{to_s.yellow}: {intro.bound.to_s}"
-               return ret
+redef class MMethodDef
+       redef fun to_s do
+               var res = new Buffer
+               if not is_intro then res.append("redef ")
+               if not mproperty.is_init then res.append("fun ")
+               if mproperty.visibility.to_s == "public" then res.append(mproperty.name.green)
+               if mproperty.visibility.to_s == "private" then res.append(mproperty.name.red)
+               if mproperty.visibility.to_s == "protected" then res.append(mproperty.name.yellow)
+               if msignature != null then res.append(msignature.to_s)
+               # FIXME: modifiers should be accessible via the model
+               #if self isa ADeferredMethPropdef then ret = "{ret} is abstract"
+               #if self isa AInternMethPropdef then ret = "{ret} is intern"
+               #if self isa AExternMethPropdef then ret = "{ret} is extern"
+               return res.to_s
        end
 end
 
-redef class AModule
-       private fun comment: String do
-               var ret = ""
-               for t in n_moduledecl.n_doc.n_comment do
-                       ret += "{t.text.replace("# ", "")}"
-               end
-               return ret
+redef class MVirtualTypeDef
+       redef fun to_s do
+               var res = new Buffer
+               if mproperty.visibility.to_s == "public" then res.append(mproperty.name.green)
+               if mproperty.visibility.to_s == "private" then res.append(mproperty.name.red)
+               if mproperty.visibility.to_s == "protected" then res.append(mproperty.name.yellow)
+               res.append(": {bound.to_s}")
+               return res.to_s
        end
 end
 
-redef class AStdClassdef
-       private fun comment: String do
-               var ret = ""
-               if n_doc != null then
-                       for t in n_doc.n_comment do
-                               var txt = t.text.replace("# ", "")
-                               txt = txt.replace("#", "")
-                               ret += "{txt}"
+redef class MSignature
+       redef fun to_s do
+               var res = new Buffer
+               if not mparameters.is_empty then
+                       res.append("(")
+                       for i in [0..mparameters.length[ do
+                               res.append(mparameters[i].to_s)
+                               if i < mparameters.length - 1 then res.append(", ")
                        end
+                       res.append(")")
                end
-               return ret
-       end
-
-       private fun short_comment: String do
-               var ret = ""
-               if n_doc != null then
-                       var txt = n_doc.n_comment.first.text
-                       txt = txt.replace("# ", "")
-                       txt = txt.replace("\n", "")
-                       ret += txt
+               if return_mtype != null then
+                       res.append(": {return_mtype.to_s}")
                end
-               return ret
+               return res.to_s
        end
 end
 
-redef class AMethPropdef
-       private fun short_comment: String do
-               var ret = ""
-               if n_doc != null then
-                       var txt = n_doc.n_comment.first.text
-                       txt = txt.replace("# ", "")
-                       txt = txt.replace("\n", "")
-                       ret += txt
-               end
-               return ret
+redef class MParameter
+       redef fun to_s do
+               var res = new Buffer
+               res.append("{name}: {mtype}")
+               if is_vararg then res.append("...")
+               return res.to_s
        end
+end
+
+redef class MNullableType
+       redef fun to_s do return "nullable {mtype}"
+end
 
+redef class MGenericType
        redef fun to_s do
-               var ret = ""
-               if not mpropdef.mproperty.is_init then
-                       ret = "fun "
+               var res = new Buffer
+               res.append("{mclass.name}[")
+               for i in [0..arguments.length[ do
+                       res.append(arguments[i].to_s)
+                       if i < arguments.length - 1 then res.append(", ")
                end
-               if mpropdef.mproperty.visibility.to_s == "public" then ret = "{ret}{mpropdef.mproperty.name.green}"
-               if mpropdef.mproperty.visibility.to_s == "private" then ret = "{ret}{mpropdef.mproperty.name.red}"
-               if mpropdef.mproperty.visibility.to_s == "protected" then ret = "{ret}{mpropdef.mproperty.name.yellow}"
-               if n_signature != null then ret = "{ret}{n_signature.to_s}"
-               if n_kwredef != null then ret = "redef {ret}"
-               if self isa ADeferredMethPropdef then ret = "{ret} is abstract"
-               if self isa AInternMethPropdef then ret = "{ret} is intern"
-               if self isa AExternMethPropdef then ret = "{ret} is extern"
-               return ret
+               res.append("]")
+               return res.to_s
        end
 end
 
-redef class ASignature
-       redef fun to_s do
-               #TODO closures
-               var ret = ""
-               if not n_params.is_empty then
-                       ret = "{ret}({n_params.join(", ")})"
+redef class MParameterType
+       redef fun to_s do return mclass.intro.parameter_names[rank]
+end
+
+redef class MVirtualType
+       redef fun to_s do return mproperty.intro.to_s
+end
+
+redef class ADoc
+       private fun comment: String do
+               var res = new Buffer
+               for t in n_comment do
+                       res.append(t.text.replace("# ", "").replace("#", ""))
                end
-               if n_type != null then ret += ": {n_type.to_s}"
-               return ret
+               return res.to_s
+       end
+
+       private fun short_comment: String do
+               return n_comment.first.text.replace("# ", "").replace("\n", "")
        end
 end
 
-redef class AParam
-       redef fun to_s do
-               var ret = "{n_id.text}"
-               if n_type != null then
-                       ret = "{ret}: {n_type.to_s}"
-                       if n_dotdotdot != null then ret = "{ret}..."
-               end
+redef class AAttrPropdef
+       private fun read_accessor: String do
+               var ret = "fun "
+               #FIXME bug with standard::stream::FDStream::fd
+               var name = mreadpropdef.mproperty.name
+               if mpropdef.mproperty.visibility.to_s == "public" then ret = "{ret}{name.green}"
+               if mpropdef.mproperty.visibility.to_s == "private" then ret = "{ret}{name.red}"
+               if mpropdef.mproperty.visibility.to_s == "protected" then ret = "{ret}{name.yellow}"
+               ret = "{ret}: {n_type.to_s}"
+               if n_kwredef != null then ret = "redef {ret}"
                return ret
        end
-end
 
-redef class AType
-       redef fun to_s do
-               var ret = n_id.text
-               if n_kwnullable != null then ret = "nullable {ret}"
-               if not n_types.is_empty then ret = "{ret}[{n_types.join(", ")}]"
+       private fun write_accessor: String do
+               var ret = "fun "
+               var name = "{mreadpropdef.mproperty.name}="
+               if n_readable != null and n_readable.n_visibility != null then
+                       if n_readable.n_visibility isa APublicVisibility then ret = "{ret}{name.green}"
+                       if n_readable.n_visibility isa APrivateVisibility then ret = "{ret}{name.red}"
+                       if n_readable.n_visibility isa AProtectedVisibility then ret = "{ret}{name.yellow}"
+               else
+                       ret = "{ret}{name.red}"
+               end
+               ret = "{ret}({mreadpropdef.mproperty.name}: {n_type.to_s})"
+               if n_kwredef != null then ret = "redef {ret}"
                return ret
        end
 end
@@ -455,10 +548,7 @@ toolcontext.process_options
 var ni = new NitIndex(toolcontext)
 ni.start
 
-# TODO seek methods by return type :<type>
-# TODO seek methods by param type: (<type>)
 # TODO seek subclasses and super classes <.<class> >.<class>
 # TODO seek subclasses and super types <:<type> >:<type>
-# TODO sort by alphabetic order
 # TODO seek with regexp
 # TODO standardize namespaces with private option