From 8857fc0570be6cc1658f84eb4879526a11d89097 Mon Sep 17 00:00:00 2001 From: Alexandre Terrasa Date: Mon, 23 Oct 2017 23:15:27 -0400 Subject: [PATCH] doc/commands: introduce model commands Signed-off-by: Alexandre Terrasa --- src/doc/commands/commands_model.nit | 515 ++++++++++++++++++++++++ src/doc/commands/tests/test_commands_model.nit | 181 +++++++++ 2 files changed, 696 insertions(+) create mode 100644 src/doc/commands/commands_model.nit create mode 100644 src/doc/commands/tests/test_commands_model.nit diff --git a/src/doc/commands/commands_model.nit b/src/doc/commands/commands_model.nit new file mode 100644 index 0000000..139afaf --- /dev/null +++ b/src/doc/commands/commands_model.nit @@ -0,0 +1,515 @@ +# This file is part of NIT ( http://www.nitlanguage.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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Doc commands about a Model or a MEntity +# +# This module defines several commands to retrieve data about a Model and MEntities. +module commands_model + +import commands_base + +import model::model_collect +import modelize +import modelbuilder +import highlight +import doc_down + +# Retrieve the MDoc related to a MEntity +class CmdComment + super CmdEntity + + # Allow fallback + # + # If `true`, the command uses `mdoc_or_fallback`. + # Default is `true`. + var fallback = true is optional, writable + + # Retrieve the full documentation + # + # If `true`, retrieves the full documentation. + # If `false`, retrieves only the synopsis. + # Default is `true`. + # + # Since the rendering the final string (md, html...) depends on the kind of + # client, the handling of this option is delegated to submodules. + var full_doc = true is optional, writable + + # Format to render the comment + # + # Can be one of `raw` or `html`. + # Default is `raw`. + var format = "raw" is optional, writable + + # MDoc to return + var mdoc: nullable MDoc = null is optional, writable + + # Same states than `CmdEntity::init_mentity` + # + # Plus returns `WarningNoMDoc` if no MDoc was found for the MEntity. + redef fun init_command do + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + if mdoc == null then + mdoc = if fallback then mentity.mdoc_or_fallback else mentity.mdoc + end + if mdoc == null then return new WarningNoMDoc(mentity) + return res + end + + # Render `mdoc` depending on `full_doc` and `format` + fun render: nullable Writable do + var mdoc = self.mdoc + if mdoc == null then return null + + if format == "html" then + if full_doc then return mdoc.html_documentation + return mdoc.html_synopsis + end + if full_doc then return mdoc.documentation + return mdoc.synopsis + end +end + +# No MDoc for `mentity` +class WarningNoMDoc + super CmdWarning + + # MEntity provided + var mentity: MEntity + + redef fun to_s do return "No documentation for `{mentity.full_name}`." +end + +# MEntity ancestors command +# +# Retrieve all the ancestors (direct and indirect) of a MEntity. +class CmdAncestors + super CmdEntityList + + # Include direct parents in the ancestors list + # + # Default is `true`. + var parents = true is optional, writable + + redef fun init_results do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + var ancestors = mentity.collect_ancestors(view).to_a + if parents then + results = ancestors + return res + end + + var parents = mentity.collect_parents(view) + var mentities = new HashSet[MEntity] + for ancestor in ancestors do + if not parents.has(ancestor) then mentities.add ancestor + end + results = mentities.to_a + return res + end +end + +# MEntity parents command +class CmdParents + super CmdEntityList + + redef fun init_results do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + results = mentity.collect_parents(view).to_a + return res + end +end + +# MEntity children command +class CmdChildren + super CmdEntityList + + redef fun init_results do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + results = mentity.collect_children(view).to_a + return res + end +end + +# MEntity descendants command +class CmdDescendants + super CmdEntityList + + # Include direct children in the descendants list + # + # Default is `true`. + var children = true is optional, writable + + redef fun init_results do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + var descendants = mentity.collect_descendants(view).to_a + if children then + results = descendants + return res + end + + var children = mentity.collect_children(view) + var mentities = new HashSet[MEntity] + for descendant in descendants do + if not children.has(descendant) then mentities.add descendant + end + results = mentities.to_a + return res + end +end + +# Linearization command +# +# Collects and linearizes definitions about an MEntity. +class CmdLinearization + super CmdEntityList + + # Same states than `CmdEntity::init_mentity` + # + # Plus returns `WarningNoLinearization` if no linearization can be computed + # from the mentity. + redef fun init_results do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + sorter = null + results = mentity.collect_linearization(view.mainmodule) + if results == null then return new WarningNoLinearization(mentity) + return res + end +end + +# No linearization computed for `mentity`. +class WarningNoLinearization + super CmdWarning + + # MEntity provided + var mentity: MEntity + + redef fun to_s do return "No linearization for `{mentity.full_name}`" +end + +# A free text search command +class CmdSearch + super CmdEntities + + # Free text command string + var query: nullable String = null is optional, writable + + # Return states: + # * `CmdSuccess`: everything was ok; + # * `ErrorNoQuery`: no `query` provided. + redef fun init_results do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + + var query = self.query + if query == null then return new ErrorNoQuery + sorter = null + results = view.find(query) + return res + end +end + +# No query string given +class ErrorNoQuery + super CmdError + + redef fun to_s do return "Missing search string" +end + +# MEntity feature list +# +# Mostly a list of mentities defined in `mentity`. +class CmdFeatures + super CmdEntityList + + # Same as `CmdEntity::init_mentity` + # + # Plus `WarningNoFeatures` if no features are found for `mentity`. + redef fun init_results do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + var mentities = new Array[MEntity] + if mentity isa MPackage then + mentities.add_all mentity.collect_mgroups(view) + mentities.add_all mentity.collect_mmodules(view) + else if mentity isa MGroup then + mentities.add_all mentity.collect_mgroups(view) + mentities.add_all mentity.collect_mmodules(view) + else if mentity isa MModule then + mentities.add_all mentity.collect_local_mclassdefs(view) + else if mentity isa MClass then + mentities.add_all mentity.collect_intro_mproperties(view) + mentities.add_all mentity.collect_redef_mpropdefs(view) + else if mentity isa MClassDef then + mentities.add_all mentity.collect_intro_mpropdefs(view) + mentities.add_all mentity.collect_redef_mpropdefs(view) + else if mentity isa MProperty then + mentities.add_all mentity.collect_mpropdefs(view) + else + return new WarningNoFeatures(mentity) + end + self.results = mentities + return res + end +end + +# TODO remove once the filters/sorters are merged +class CmdIntros + super CmdEntityList + + redef fun init_results do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + if mentity isa MModule then + var mentities = mentity.collect_intro_mclasses(view).to_a + self.results = mentities + else if mentity isa MClass then + var mentities = mentity.collect_intro_mproperties(view).to_a + self.results = mentities + else if mentity isa MClassDef then + var mentities = mentity.collect_intro_mpropdefs(view).to_a + view.mainmodule.linearize_mpropdefs(mentities) + self.results = mentities + else + return new WarningNoFeatures(mentity) + end + return res + end +end + +# TODO remove once the filters/sorters are merged +class CmdRedefs + super CmdEntityList + + redef fun init_command do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + if mentity isa MModule then + var mentities = mentity.collect_redef_mclasses(view).to_a + self.results = mentities + else if mentity isa MClass then + var mentities = mentity.collect_redef_mproperties(view).to_a + self.results = mentities + else if mentity isa MClassDef then + var mentities = mentity.collect_redef_mpropdefs(view).to_a + view.mainmodule.linearize_mpropdefs(mentities) + self.results = mentities + else + return new WarningNoFeatures(mentity) + end + return res + end +end + +# TODO remove once the filters/sorters are merged +class CmdAllProps + super CmdEntityList + + redef fun init_results do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + if mentity isa MClass then + results = mentity.collect_accessible_mproperties(view).to_a + else + return new WarningNoFeatures(mentity) + end + return res + end +end + +# No feature list for `mentity` +class WarningNoFeatures + super CmdWarning + + # MEntity provided + var mentity: MEntity + + redef fun to_s do return "No features for `{mentity.full_name}`" +end + +# Cmd that finds the source code related to an `mentity` +class CmdCode + super CmdEntity + + autoinit(view, modelbuilder, mentity, mentity_name, format) + + # ModelBuilder used to get AST nodes + var modelbuilder: ModelBuilder + + # AST node to return + var node: nullable ANode = null is optional, writable + + # Rendering format + # + # Set the output format for this piece of code. + # Can be "raw" or "html". + # Default is "raw". + # + # This format can be different than the format used in the command response. + # For example you can choose to render code as HTML inside a JSON object response. + # Another example is to render raw format to put into a HTML code tag. + var format = "raw" is optional, writable + + # Same as `CmdEntity::init_mentity` + # + # Plus `WarningNoCode` if no code/AST node is found for `mentity`. + redef fun init_command do + if node != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + var mentity = self.mentity.as(not null) + + if mentity isa MClass then mentity = mentity.intro + if mentity isa MProperty then mentity = mentity.intro + node = modelbuilder.mentity2node(mentity) + if node == null then return new WarningNoCode(mentity) + return res + end + + # Render `node` depending on the selected `format` + fun render: nullable Writable do + var node = self.node + if node == null then return null + if format == "html" then + var hl = new HighlightVisitor + hl.enter_visit node + return hl.html + end + # TODO make a raw visitor + return node.to_s + end +end + +# No code for `mentity` +class WarningNoCode + super CmdWarning + + # MEntity provided + var mentity: MEntity + + redef fun to_s do return "No code for `{mentity.full_name}`" +end + +# Model commands + +# A command that returns a list of all mentities in a model +class CmdModelEntities + super CmdEntities + + # Kind of mentities to be returned. + # + # Value must be one of "packages", "groups", "modules", "classes", "classdefs", + # "properties", "propdefs" or "all". + # + # Default is "all". + var kind = "all" is optional, writable + + # Default limit is `10` + redef var limit = 10 + + redef fun init_results do + if results != null then return new CmdSuccess + + var res = super + if not res isa CmdSuccess then return res + + var mentities = new Array[MEntity] + if kind == "packages" then + mentities = view.mpackages.to_a + else if kind == "groups" then + mentities = view.mgroups.to_a + else if kind == "modules" then + mentities = view.mmodules.to_a + else if kind == "classes" then + mentities = view.mclasses.to_a + else if kind == "classdefs" then + mentities = view.mclassdefs.to_a + else if kind == "properties" then + mentities = view.mproperties.to_a + else if kind == "propdefs" then + mentities = view.mpropdefs.to_a + else + mentities = view.mentities.to_a + end + results = mentities + return res + end +end + +# A command that returns a random list of mentities from a model +class CmdRandomEntities + super CmdModelEntities + + # Always return `CmdSuccess` + redef fun init_results do + if results != null then return new CmdSuccess + var res = super + if not res isa CmdSuccess then return res + randomize + return res + end + + # Randomize mentities order + fun randomize do + var results = self.results + if results == null then return + results.shuffle + end +end diff --git a/src/doc/commands/tests/test_commands_model.nit b/src/doc/commands/tests/test_commands_model.nit new file mode 100644 index 0000000..9cdfc96 --- /dev/null +++ b/src/doc/commands/tests/test_commands_model.nit @@ -0,0 +1,181 @@ +# This file is part of NIT ( http://www.nitlanguage.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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module test_commands_model is test + +import test_commands +import doc::commands::commands_model + +class TestCommandsModel + super TestCommands + test + + # CmdEntity + + fun test_cmd_entity is test do + var cmd = new CmdEntity(test_view, mentity_name = "test_prog::Character") + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.mentity.as(not null).full_name == "test_prog::Character" + end + + fun test_cmd_entity_not_found is test do + var cmd = new CmdEntity(test_view, mentity_name = "test_prog::Characterzz") + var res = cmd.init_command + assert res isa ErrorMEntityNotFound + assert res.suggestions.first.full_name == "test_prog::Character" + end + + fun test_cmd_entity_conflict is test do + var cmd = new CmdEntity(test_view, mentity_name = "+") + var res = cmd.init_command + assert res isa ErrorMEntityConflict + assert res.conflicts.length == 2 + end + + fun test_cmd_entity_no_name is test do + var cmd = new CmdEntity(test_view) + var res = cmd.init_command + assert res isa ErrorMEntityNoName + end + + # CmdComment + + fun test_cmd_comment is test do + var cmd = new CmdComment(test_view, mentity_name = "test_prog::Character") + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.mdoc != null + end + + fun test_cmd_comment_no_mdoc is test do + var cmd = new CmdComment(test_view, mentity_name = "test_prog::Character", fallback = false) + var res = cmd.init_command + assert res isa WarningNoMDoc + end + + # CmdInheritance + + fun test_cmd_parents is test do + var cmd = new CmdParents(test_view, mentity_name = "test_prog::Warrior") + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.results.as(not null).length == 1 + end + + fun test_cmd_ancestors is test do + var cmd = new CmdAncestors(test_view, mentity_name = "test_prog::Warrior") + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.results.as(not null).length == 2 + end + + fun test_cmd_ancestorsi_without_parents is test do + var cmd = new CmdAncestors(test_view, mentity_name = "test_prog::Warrior", parents = false) + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.results.as(not null).length == 1 + end + + fun test_cmd_children is test do + var cmd = new CmdChildren(test_view, mentity_name = "test_prog::Career") + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.results.as(not null).length == 3 + end + + fun test_cmd_descendants is test do + var cmd = new CmdDescendants(test_view, mentity_name = "test_prog::Career", children = false) + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.results.as(not null).length == 0 + end + + # CmdSearch + + fun test_cmd_search is test do + var cmd = new CmdSearch(test_view, query = "Carer") + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.results.as(not null).first.full_name == "test_prog::Career" + end + + fun test_cmd_search_no_query is test do + var cmd = new CmdSearch(test_view) + var res = cmd.init_command + assert res isa ErrorNoQuery + end + + # CmdFeatures + + fun test_cmd_features is test do + var cmd = new CmdFeatures(test_view, mentity_name = "test_prog::Career") + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.results.as(not null).length == 10 + end + + fun test_cmd_features_no_features is test do + var cmd = new CmdFeatures(test_view, mentity_name = "test_prog$Career$strength_bonus") + var res = cmd.init_command + assert res isa WarningNoFeatures + end + + # CmdLinearization + + fun test_cmd_lin is test do + var cmd = new CmdLinearization(test_view, mentity_name = "init") + var res = cmd.init_command + assert res isa CmdSuccess + print cmd.results.as(not null) + assert cmd.results.as(not null).length == 10 + end + + fun test_cmd_lin_no_lin is test do + var cmd = new CmdLinearization(test_view, mentity_name = "test_prog") + var res = cmd.init_command + assert res isa WarningNoLinearization + end + + # CmdCode + + fun test_cmd_code is test do + var cmd = new CmdCode(test_view, test_builder, mentity_name = "test_prog::Career") + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.node isa AStdClassdef + end + + fun test_cmd_code_no_code is test do + var cmd = new CmdCode(test_view, test_builder, mentity_name = "test_prog") + var res = cmd.init_command + assert res isa WarningNoCode + end + + # CmdModel + + fun test_cmd_results is test do + var cmd = new CmdModelEntities(test_view, kind = "modules") + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.results.as(not null).length == 9 + end + + fun test_cmd_results_random is test do + var cmd = new CmdRandomEntities(test_view, kind = "packages") + var res = cmd.init_command + assert res isa CmdSuccess + assert cmd.results.as(not null).length == 2 + end +end -- 1.7.9.5