--- /dev/null
+# 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
--- /dev/null
+# 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