X-Git-Url: http://nitlanguage.org diff --git a/src/nitx.nit b/src/nitx.nit index dcf71d2..7162e43 100644 --- a/src/nitx.nit +++ b/src/nitx.nit @@ -12,672 +12,165 @@ # See the License for the specific language governing permissions and # limitations under the License. -# nit index, is a command tool used to display documentation +# `nitx`, a command tool that displays useful data about Nit code +# +# Features: +# +# * Display documentation from name/namespace +# * Find type usage in parameters, returns and news. +# * Find usage (calls) of a specific property. +# * Find source code related to class/property by its name. +# * Display inheritance lists module nitx -import model_utils -import modelize_property +import frontend +import doc::term +import prompt + +redef class ToolContext + + # Used to shortcut the prompt and display directly the result in console. + var opt_command = new OptionString("Nitx command to perform", "-c", "--command") -private class Pager - var content = new Buffer - fun add(text: String) do addn("{text}\n") - 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") + # Compute and use the catalog + var opt_catalog = new OptionBool("Use catalog", "--catalog") + + init do option_context.add_option(opt_command, opt_catalog) 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 - private var mbuilder: ModelBuilder - private var mainmodule: MModule - private var arguments: Array[String] +# Nitx handles console queries +# +# Using `prompt`, the command line can be turned on an interactive tool. +class Nitx - 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 + # Model that contains the informations to display + var model: Model - if arguments.is_empty or arguments.length > 2 then - print "usage: ni path/to/module.nit [expression]" - toolcontext.option_context.usage - exit(1) - end + # Mainmodule for class linearization + var mainmodule: MModule - model = new Model - mbuilder = new ModelBuilder(model, toolcontext) + # ModelBuilder to access AST nodes + var modelbuilder: ModelBuilder - # Here we load an process std modules - #var dir = "NIT_DIR".environ - #var mmodules = modelbuilder.parse_and_build(["{dir}/lib/standard/standard.nit"]) - var mmodules = mbuilder.parse([arguments.first]) - if mmodules.is_empty then return - mbuilder.run_phases - assert mmodules.length == 1 - self.mainmodule = mmodules.first - end + # Catalog if any + var catalog: nullable Catalog = null is optional + + # Do not use colors in the output + var no_color = false is optional + # Displays the welcome message and start prompt. fun start do - if arguments.length == 1 then - welcome - prompt - else - seek(arguments[1]) - end + welcome + prompt end + # Displays the welcome message and the list of loaded modules. fun welcome do - print "Welcome in the Nit Index." + print "Welcome in the Nit Index!" print "" - print "Loaded 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}" + print "Loaded packages:\n" + var cmd = new CmdModelEntities(model, kind = "packages") + cmd.init_command + for mpackage in cmd.results.as(not null) do + print " * {mpackage.full_name}" end - print "" help end + # Displays the list of available queries. fun help do - 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 ':q' to exit" - print "\tEnter ':h' to display this help message" + # TODO automatize that + print "\nCommands:\n" + for usage, doc in parser.commands_usage do + var l = usage.length / 8 + print "\t{usage}{"\t" * (3 - l)}{doc}" + end + print "\n" + print "\t:h\t\t\tdisplay this help message" + print "\t:q\t\t\tquit interactive mode" print "" end + # Prompts the user for a query. fun prompt do - printn ">> " - seek(stdin.read_line) - end - - fun seek(entry: String) do - if entry.is_empty then - prompt - return - end - if entry == ":h" then - help - prompt - return - end - if entry == ":q" then exit(0) - var pager = new Pager - # seek return types - if entry.has_prefix("return:") then - var ret = entry.split_with(":")[1].replace(" ", "") - var matches = seek_returns(ret) - props_fulldoc(pager, matches) - else if entry.has_prefix("param:") then - var param = entry.split_with(":")[1].replace(" ", "") - var matches = seek_params(param) - props_fulldoc(pager, matches) - else - # seek for modules - var mmatches = new List[MModule] - for m in model.mmodules do - if m.name == entry then mmatches.add(m) - end - if not mmatches.is_empty then modules_fulldoc(pager, mmatches) - # seek for classes - var cmatches = new List[MClass] - for c in model.mclasses do - if c.name == entry then cmatches.add(c) - end - if not cmatches.is_empty then classes_fulldoc(pager, cmatches) - # seek for properties - var matches = new List[MProperty] - for p in model.mproperties do - if p.name == entry then matches.add(p) - end - if not matches.is_empty then props_fulldoc(pager, matches) - end - # no matches - if pager.content.is_empty then - print "Nothing known about '{entry}', type ':h' for help" - else - pager.render - end - if arguments.length == 1 then prompt - end - - private fun modules_fulldoc(pager: Pager, mmodules: List[MModule]) do - for mmodule in mmodules do - # name and prototype - pager.add("# {mmodule.namespace}\n".bold) - # comment - if mbuilder.mmodule2nmodule.has_key(mmodule) then - var nmodule = mbuilder.mmodule2nmodule[mmodule] - if not nmodule.n_moduledecl.n_doc == null then - for comment in nmodule.n_moduledecl.n_doc.comment do pager.add(comment.green) - end - end - pager.add("{mmodule.prototype}\n") - # imports - var msorter = new MModuleNameSorter - var ms = mmodule.in_importation.greaters.to_a - if ms.length > 1 then - msorter.sort(ms) - pager.add("## imported modules".bold) - pager.addn("\t") - for i in [0..ms.length[ do - if ms[i] == mmodule then continue - pager.addn(ms[i].name) - if i < ms.length - 1 then pager.addn(", ") - end - pager.add("\n") - end - # clients - ms = mmodule.in_importation.smallers.to_a - if ms.length > 1 then - msorter.sort(ms) - pager.add("## known modules".bold) - pager.addn("\t") - for i in [0..ms.length[ do - if ms[i] == mmodule then continue - pager.addn(ms[i].name) - if i < ms.length - 1 then pager.addn(", ") - end - pager.add("\n") - end - # local classes and interfaces - var sorter = new MClassDefNameSorter - var intro_mclassdefs = new Array[MClassDef] - var redef_mclassdefs = new Array[MClassDef] - for mclassdef in mmodule.mclassdefs do - if mclassdef.is_intro then - intro_mclassdefs.add(mclassdef) - else - redef_mclassdefs.add(mclassdef) - end - end - # intro - if not intro_mclassdefs.is_empty then - sorter.sort(intro_mclassdefs) - pager.add("\n## introduced classes".bold) - for mclassdef in intro_mclassdefs do - pager.add("") - var nclass = mbuilder.mclassdef2nclassdef[mclassdef] - if nclass isa AStdClassdef and not nclass.n_doc == null and not nclass.n_doc.short_comment.is_empty then - pager.add("\t{nclass.n_doc.short_comment.green}") - end - pager.add("\t{mclassdef.mclass.prototype}") - #TODO add redefs? - end - end - # redefs - if not redef_mclassdefs.is_empty then - sorter.sort(redef_mclassdefs) - pager.add("\n## refined classes".bold) - for mclassdef in redef_mclassdefs do - pager.add("") - #TODO intro comment? - var nclass = mbuilder.mclassdef2nclassdef[mclassdef] - if nclass isa AStdClassdef and not nclass.n_doc == null and not nclass.n_doc.short_comment.is_empty then - pager.add("\t# {nclass.n_doc.short_comment.green}") - end - pager.add("\t{mclassdef.mclass.prototype}") - pager.add("\t\t" + "introduced in {mclassdef.mclass.intro.mmodule.namespace.bold}".gray) - for odef in mclassdef.mclass.mclassdefs do - if odef.is_intro or odef == mclassdef or mclassdef.mmodule == mmodule then continue - pager.add("\t\t" + "refined in {mclassdef.mmodule.namespace.bold}".gray) - end - end - end - #TODO add inherited classes? - pager.add_rule - end - end - - private fun classes_fulldoc(pager: Pager, mclasses: List[MClass]) do - for mclass in mclasses do - # title - pager.add("# {mclass.namespace}\n".bold) - # comment - if mbuilder.mclassdef2nclassdef.has_key(mclass.intro) then - var nclass = mbuilder.mclassdef2nclassdef[mclass.intro] - if nclass isa AStdClassdef and not nclass.n_doc == null then - for comment in nclass.n_doc.comment do pager.add(comment.green) - end - end - pager.addn("{mclass.prototype}") - if not mclass.in_hierarchy(mainmodule).direct_greaters.is_empty then - var supers = mclass.in_hierarchy(mainmodule).direct_greaters.to_a - pager.addn(" super ") - for i in [0..supers.length[ do - if supers[i] == mclass then continue - pager.addn("{supers[i].name}{supers[i].signature}") - if i < mclass.in_hierarchy(mainmodule).direct_greaters.length -1 then pager.addn(", ") - end - end - pager.add("\n") - # formal types - if not mclass.parameter_types.is_empty then - pager.add("## formal types".bold) - for ft, bound in mclass.parameter_types do - pager.add("") - pager.add("\t{ft.to_s.green}: {bound.to_console}") - end - pager.add("") - end - # get properties - var cats = new ArrayMap[String, Set[MPropDef]] - cats["virtual types"] = new HashSet[MPropDef] - cats["constructors"] = new HashSet[MPropDef] - cats["introduced methods"] = new HashSet[MPropDef] - cats["refined methods"] = new HashSet[MPropDef] - - for mclassdef in mclass.mclassdefs do - for mpropdef in mclassdef.mpropdefs do - if mpropdef isa MAttributeDef then continue - if mpropdef isa MVirtualTypeDef then cats["virtual types"].add(mpropdef) - if mpropdef isa MMethodDef then - if mpropdef.mproperty.is_init then - cats["constructors"].add(mpropdef) - else if mpropdef.is_intro then - cats["introduced methods"].add(mpropdef) - else - cats["refined methods"].add(mpropdef) - end - end - end - end - # local mproperties - for cat, list in cats do - if not list.is_empty then - #sort list - var sorted = new Array[MPropDef] - sorted.add_all(list) - var sorter = new MPropDefNameSorter - sorter.sort(sorted) - pager.add("## {cat}".bold) - for mpropdef in sorted do - pager.add("") - if mbuilder.mpropdef2npropdef.has_key(mpropdef) then - var nprop = mbuilder.mpropdef2npropdef[mpropdef] - if not nprop.n_doc == null and not nprop.n_doc.comment.is_empty then - for comment in nprop.n_doc.comment do pager.add("\t{comment.green}") - else - nprop = mbuilder.mpropdef2npropdef[mpropdef.mproperty.intro] - if not nprop.n_doc == null and not nprop.n_doc.comment.is_empty then - for comment in nprop.n_doc.comment do pager.add("\t{comment.green}") - end - end - end - pager.add("\t{mpropdef.to_console}") - mainmodule.linearize_mpropdefs(mpropdef.mproperty.mpropdefs) - var previous_defs = new Array[MPropDef] - for def in mpropdef.mproperty.mpropdefs do - if def == mpropdef then continue - if def.is_intro then continue - if mclass.in_hierarchy(mainmodule) < def.mclassdef.mclass then - previous_defs.add(def) - end - end - if not mpropdef.is_intro then - pager.add("\t\t" + "introduced by {mpropdef.mproperty.intro.mclassdef.namespace.bold}".gray) - end - if not previous_defs.is_empty then - for def in previous_defs do pager.add("\t\t" + "inherited from {def.mclassdef.namespace.bold}".gray) - end - end - pager.add("") - end - end - # inherited mproperties - var inhs = new ArrayMap[MClass, Array[MProperty]] - var ancestors = mclass.in_hierarchy(mainmodule).greaters.to_a - mainmodule.linearize_mclasses(ancestors) - for a in ancestors do - if a == mclass then continue - for c in a.mclassdefs do - for p in c.intro_mproperties do - if p.intro_mclassdef == c then - if not inhs.has_key(a) then inhs[a] = new Array[MProperty] - inhs[a].add(p) - end - end - end - end - if not inhs.is_empty then - pager.add("## inherited properties".bold) - for a, ps in inhs do - pager.add("\n\tfrom {a.namespace.bold}: {ps.join(", ")}") - end - end - pager.add_rule - end - end - - private fun props_fulldoc(pager: Pager, raw_mprops: List[MProperty]) do - # group by module - var cats = new HashMap[MModule, Array[MProperty]] - for mprop in raw_mprops do - if mprop isa MAttribute then continue - var key = mprop.intro.mclassdef.mmodule - if not cats.has_key(key) then cats[key] = new Array[MProperty] - cats[key].add(mprop) - end - #sort groups - var sorter = new MModuleNameSorter - var sorted = new Array[MModule] - sorted.add_all(cats.keys) - sorter.sort(sorted) - # display - for mmodule in sorted do - var mprops = cats[mmodule] - pager.add("# matches in module {mmodule.namespace.bold}") - var sorterp = new MPropertyNameSorter - sorterp.sort(mprops) - for mprop in mprops do - pager.add("") - if mbuilder.mpropdef2npropdef.has_key(mprop.intro) then - var nprop = mbuilder.mpropdef2npropdef[mprop.intro] - if not nprop.n_doc == null and not nprop.n_doc.comment.is_empty then - for comment in nprop.n_doc.comment do pager.add("\t{comment.green}") - end - end - pager.add("\t{mprop.intro.to_console}") - pager.add("\t\t" + "introduced in {mprop.intro_mclassdef.namespace.bold}".gray) - var mpropdefs = mprop.mpropdefs - mainmodule.linearize_mpropdefs(mpropdefs) - for mpdef in mpropdefs do - if not mpdef.is_intro then - pager.add("\t\t" + "refined in {mpdef.mclassdef.namespace.bold}".gray) - end - end - end - pager.add_rule - end - 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 -end - -# Printing facilities - -redef class MModule - # prototype of the module - # module ownername::name - private fun prototype: String do return "module {name}" - - # namespace of the module - # ownername::name - private fun namespace: String do - if public_owner == null then - return self.name + var line = sys.prompt(">> ", true) + if line != null then + do_command(line) else - return "{public_owner.namespace}::{self.name}" + # EOF + exit 0 end + prompt end -end -redef class MClass - # return the generic signature of the class - # [E, F] - private fun signature: String do - var res = new Buffer - if arity > 0 then - res.append("[") - for i in [0..intro.parameter_names.length[ do - res.append(intro.parameter_names[i]) - if i < intro.parameter_names.length - 1 then res.append(", ") - end - res.append("]") - end - return res.to_s - end - - # return the prototype of the class - # class name is displayed with colors depending on visibility - # abstract interface Foo[E] - private fun prototype: String do - var res = new Buffer - res.append("{kind} ") - if visibility.to_s == "public" then res.append("{name}{signature}".bold.green) - if visibility.to_s == "private" then res.append("{name}{signature}".bold.red) - if visibility.to_s == "protected" then res.append("{name}{signature}".bold.yellow) - return res.to_s - end + # Parser used to process doc commands + var parser = new CommandParser(model, mainmodule, modelbuilder, catalog) is lazy - private fun namespace: String do - return "{intro_mmodule.namespace}::{name}" - end -end - -redef class MClassDef - private fun namespace: String do - return "{mmodule.full_name}::{mclass.name}" - end -end - -redef class MProperty - fun to_console: String do - if visibility.to_s == "public" then return name.green - if visibility.to_s == "private" then return name.red - if visibility.to_s == "protected" then return name.yellow - return name.bold - end -end - -redef class MPropDef - fun to_console: String is abstract -end - -redef class MMethodDef - redef fun to_console do - var res = new Buffer - if not is_intro then res.append("redef ") - if not mproperty.is_init then res.append("fun ") - res.append(mproperty.to_console.bold) - if msignature != null then res.append(msignature.to_console) - # 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 MVirtualTypeDef - redef fun to_console do - var res = new Buffer - res.append("type ") - res.append(mproperty.to_console.bold) - res.append(": {bound.to_console}") - return res.to_s - end -end - -redef class MSignature - redef fun to_console 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_console) - if i < mparameters.length - 1 then res.append(", ") - end - res.append(")") - end - if return_mtype != null then - res.append(": {return_mtype.to_console}") - end - return res.to_s - end -end - -redef class MParameter - fun to_console: String do - var res = new Buffer - res.append("{name}: {mtype.to_console}") - if is_vararg then res.append("...") - return res.to_s - end -end - -redef class MType - fun to_console: String do return self.to_s -end - -redef class MNullableType - redef fun to_console do return "nullable {mtype.to_console}" -end - -redef class MGenericType - redef fun to_console do - var res = new Buffer - res.append("{mclass.name}[") - for i in [0..arguments.length[ do - res.append(arguments[i].to_console) - if i < arguments.length - 1 then res.append(", ") + # Processes the query string and performs it. + fun do_command(str: String) do + if str == ":q" then + exit 0 + else if str == ":h" then + help + return end - res.append("]") - return res.to_s + parser.execute(str, no_color) end end -redef class MParameterType - redef fun to_console do return mclass.intro.parameter_names[rank] -end - -redef class MVirtualType - redef fun to_console do return mproperty.name -end - -redef class ADoc - private fun comment: List[String] do - var res = new List[String] - for t in n_comment do - res.add(t.text.replace("\n", "")) +redef class Catalog + # Build the catalog for Nitx + private fun build_catalog(model: Model, filter: nullable ModelFilter) do + # Scan all modules of collected packages + for p in model.collect_mpackages(filter) do + var g = p.root + assert g != null + modelbuilder.scan_group(g) end - return res - end - - private fun short_comment: String do - return n_comment.first.text.replace("\n", "") - end -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 - - 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 - -# Redef String class to add a function to color the string -redef class String - - private fun add_escape_char(escapechar: String): String do - return "{escapechar}{self}\\033[0m" - end - - private fun esc: Char do return 27.ascii - private fun gray: String do return add_escape_char("{esc}[30m") - private fun red: String do return add_escape_char("{esc}[31m") - private fun green: String do return add_escape_char("{esc}[32m") - private fun yellow: String do return add_escape_char("{esc}[33m") - private fun blue: String do return add_escape_char("{esc}[34m") - private fun purple: String do return add_escape_char("{esc}[35m") - private fun cyan: String do return add_escape_char("{esc}[36m") - private fun light_gray: String do return add_escape_char("{esc}[37m") - private fun bold: String do return add_escape_char("{esc}[1m") - private fun underline: String do return add_escape_char("{esc}[4m") - - private fun escape: String - do - var b = new Buffer - for c in self do - if c == '\n' then - b.append("\\n") - else if c == '\0' then - b.append("\\0") - else if c == '"' then - b.append("\\\"") - else if c == '\\' then - b.append("\\\\") - else if c == '`' then - b.append("'") - else if c.ascii < 32 then - b.append("\\{c.ascii.to_base(8, false)}") - else - b.add(c) - end + # Build the catalog + for mpackage in model.collect_mpackages(filter) do + package_page(mpackage) + git_info(mpackage) + mpackage_stats(mpackage) end - return b.to_s end end -# Create a tool context to handle options and paths +# build toolcontext var toolcontext = new ToolContext -toolcontext.process_options - -# Here we launch the nit index -var ni = new NitIndex(toolcontext) -ni.start - -# TODO seek subclasses and super classes <. >. -# TODO seek subclasses and super types <: >: -# TODO seek with regexp -# TODO standardize namespaces with private option +var tpl = new Template +tpl.add "Usage: nitx [OPTION]... ... [query]\n" +tpl.add "Displays pieces of API information from Nit source files." +toolcontext.tooldescription = tpl.write_to_string + +# process options +toolcontext.process_options(args) +var arguments = toolcontext.option_context.rest + +# build model +var model = new Model +var mbuilder = new ModelBuilder(model, toolcontext) +var mmodules = mbuilder.parse_full(arguments) + +# process +if mmodules.is_empty then return +mbuilder.run_phases +toolcontext.run_global_phases(mmodules) +var mainmodule = toolcontext.make_main_module(mmodules) + +# build views +var catalog = null +if toolcontext.opt_catalog.value then + catalog = new Catalog(mbuilder) + catalog.build_catalog(model) +end + +# start nitx +var nitx = new Nitx(model, mainmodule, mbuilder, catalog, toolcontext.opt_no_color.value) +var q = toolcontext.opt_command.value +if q != null then # shortcut prompt + print "" + nitx.do_command(q) + return +end +nitx.start