doc/commands: introduce model commands
authorAlexandre Terrasa <alexandre@moz-code.org>
Tue, 24 Oct 2017 03:15:27 +0000 (23:15 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Thu, 23 Nov 2017 16:08:40 +0000 (11:08 -0500)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

src/doc/commands/commands_model.nit [new file with mode: 0644]
src/doc/commands/tests/test_commands_model.nit [new file with mode: 0644]

diff --git a/src/doc/commands/commands_model.nit b/src/doc/commands/commands_model.nit
new file mode 100644 (file)
index 0000000..139afaf
--- /dev/null
@@ -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 (file)
index 0000000..9cdfc96
--- /dev/null
@@ -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