diff --git a/src/cminx/aggregator.py b/src/cminx/aggregator.py index 71d43e0..1eae4b0 100755 --- a/src/cminx/aggregator.py +++ b/src/cminx/aggregator.py @@ -24,10 +24,11 @@ class listens to all relevent parser rules, generates :Author: Branden Butler :License: Apache 2.0 """ - +import itertools import logging import re from dataclasses import dataclass +from enum import Enum from typing import List, Union from antlr4 import ParserRuleContext @@ -35,7 +36,7 @@ class listens to all relevent parser rules, generates from .documentation_types import AttributeDocumentation, FunctionDocumentation, MacroDocumentation, \ VariableDocumentation, GenericCommandDocumentation, ClassDocumentation, TestDocumentation, SectionDocumentation, \ MethodDocumentation, VarType, CTestDocumentation, ModuleDocumentation, AbstractCommandDefinitionDocumentation, \ - OptionDocumentation, DanglingDoccomment, DocumentationType + OptionDocumentation, DanglingDoccomment, DocumentationType, SetCommandKeywords, CacheVarType from .exceptions import CMakeSyntaxException from .parser.CMakeListener import CMakeListener @@ -45,6 +46,8 @@ class listens to all relevent parser rules, generates from cminx import Settings + + @dataclass class DefinitionCommand: """ @@ -283,7 +286,7 @@ def process_set(self, ctx: CMakeParser.Command_invocationContext, docstring: str :param docstring: Cleaned docstring. """ - + params = [val.getText() for val in ctx.single_argument()] if len(ctx.single_argument()) < 1: pretty_text = docstring pretty_text += f"\n{ctx.getText()}" @@ -292,17 +295,31 @@ def process_set(self, ctx: CMakeParser.Command_invocationContext, docstring: str f"set() called with incorrect parameters: {ctx.single_argument()}\n\n{pretty_text}") return - varname = ctx.single_argument()[ - 0].getText() - # First argument is name of variable so ignore that - arg_len = len(ctx.single_argument()) - 1 + varname = params[0] + # Used for comparing against the set() command's keywords + params_upper = [param.upper() for param in params[1:]] + values = list( + itertools.takewhile( + lambda param: param not in SetCommandKeywords.values(), + params[1:] + ) + ) - if arg_len > 1: # List - values = [val.getText() - for val in ctx.single_argument()[1:]] + keywords = {kw: kw.value in params_upper for kw in SetCommandKeywords} + + if keywords[SetCommandKeywords.CACHE]: + cache_keyword_index = params.index(SetCommandKeywords.CACHE.value) + cache_var_type = CacheVarType.values()[params[cache_keyword_index + 1]] + cache_var_docstring = params[cache_keyword_index + 2] + else: + cache_var_type = None + cache_var_docstring = "" + + if len(values) > 1: # List self.documented.append(VariableDocumentation( - varname, docstring, VarType.LIST, " ".join(values))) - elif arg_len == 1: # String + varname, docstring, VarType.LIST, " ".join(values), keywords, cache_var_type, cache_var_docstring + )) + elif len(values) == 1: # String value = ctx.single_argument()[1].getText() # If the value includes the quote marks, @@ -312,10 +329,12 @@ def process_set(self, ctx: CMakeParser.Command_invocationContext, docstring: str if value[-1] == '"': value = value[:-1] self.documented.append(VariableDocumentation( - varname, docstring, VarType.STRING, value)) + varname, docstring, VarType.STRING, value, keywords, cache_var_type, cache_var_docstring + )) else: # Unset self.documented.append(VariableDocumentation( - varname, docstring, VarType.UNSET, None)) + varname, docstring, VarType.UNSET, None, keywords + )) def process_cpp_class(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None: """ @@ -493,13 +512,16 @@ def process_option(self, ctx: CMakeParser.Command_invocationContext, docstring: pretty_text += f"\n{ctx.getText()}" self.logger.error( - f"ct_add_section() called with incorrect parameters: {params}\n\n{pretty_text}") + f"option() called with incorrect parameters: {params}\n\n{pretty_text}") return + option_doc = OptionDocumentation( params[0], docstring, "bool", params[2] if len(params) == 3 else None, + {}, + None, params[1] ) self.documented.append(option_doc) diff --git a/src/cminx/documentation_types.py b/src/cminx/documentation_types.py index d202646..de500d0 100644 --- a/src/cminx/documentation_types.py +++ b/src/cminx/documentation_types.py @@ -27,11 +27,37 @@ from abc import ABC, abstractmethod from dataclasses import dataclass, field from enum import Enum -from typing import Union, List +from typing import Union, List, Dict from .rstwriter import RSTWriter, Directive, interpreted_text +class SetCommandKeywords(Enum): + CACHE = "CACHE" + PARENT_SCOPE = "PARENT_SCOPE" + BOOL = "BOOL" + PATH = "PATH" + STRING = "STRING" + INTERNAL = "INTERNAL" + FORCE = "FORCE" + + @classmethod + def values(cls) -> List[str]: + return [param.value for param in cls] + + +class CacheVarType(Enum): + BOOL = "BOOL" + FILEPATH = "FILEPATH" + PATH = "PATH" + STRING = "STRING" + INTERNAL = "INTERNAL" + + @classmethod + def values(cls) -> Dict[str, 'CacheVarType']: + return {param.value: param for param in cls} + + class VarType(Enum): """The types of variables accepted by the CMake :code:`set()` command""" @@ -145,9 +171,23 @@ class VariableDocumentation(DocumentationType): value: Union[str, None] """A default value that the variable has""" + keywords: Dict[SetCommandKeywords, bool] + """ + A dictionary between the set() command keywords and whether + they were present / active in the documented call. + """ + + cache_var_type: Union[CacheVarType, None] = None + + cache_var_docstring: str = "" + def process(self, writer: RSTWriter) -> None: d = writer.directive("data", f"{self.name}") d.text(self.doc) + if self.cache_var_type is not None: + d.text("Cache help text:") + d.text(self.cache_var_docstring) + d.field("FORCE", str(self.keywords[SetCommandKeywords.FORCE])) d.field("Default value", self.value) if self.type == VarType.STRING: var_type = "str" @@ -157,7 +197,7 @@ def process(self, writer: RSTWriter) -> None: var_type = "UNSET" else: raise ValueError(f"Unknown variable type: {self.type}") - d.field("type", var_type) + d.field("type", var_type if self.cache_var_type is None else self.cache_var_type.value) @dataclass @@ -167,12 +207,10 @@ class OptionDocumentation(VariableDocumentation): representing a user-configurable option that can be selected via the cache. The RST form of this documentation also uses the :code:`data` directive, but includes a note - specifying the variable is an option. + specifying the variable is an option. The help text is + the cache_var_docstring field. """ - help_text: str - """The help text that the option has.""" - def process(self, writer: RSTWriter) -> None: d = writer.directive("data", f"{self.name}") note = d.directive("note") @@ -183,7 +221,7 @@ def process(self, writer: RSTWriter) -> None: edited on the command line by the :code:`-D` flag. """)) d.text(self.doc) - d.field("Help text", self.help_text) + d.field("Help text", self.cache_var_docstring) d.field("Default value", self.value if self.value is not None else "OFF") d.field("type", self.type) diff --git a/tests/examples/example.cmake b/tests/examples/example.cmake index 864ad9e..662c43c 100644 --- a/tests/examples/example.cmake +++ b/tests/examples/example.cmake @@ -96,6 +96,12 @@ set(MyList "Value" "Value 2") #]] set(MyString "String") +#[[[ +# This is an example of a cache variable. +# This variable is a string variable. +#]] +set(MyStringCache "String" CACHE STRING "Some string a user can set in cache.") + #[[ # This is an undocumented variable. # Unlike most other elements, it will diff --git a/tests/examples/sphinx/source/example.rst b/tests/examples/sphinx/source/example.rst index 45fc64b..2883b7b 100644 --- a/tests/examples/sphinx/source/example.rst +++ b/tests/examples/sphinx/source/example.rst @@ -93,6 +93,21 @@ examples.example :type: str +.. data:: MyStringCache + + This is an example of a cache variable. + This variable is a string variable. + + Cache help text: + "Some string a user can set in cache." + + :FORCE: False + + :Default value: String + + :type: STRING + + .. function:: message("hello") diff --git a/tests/examples/sphinx/source/example_no_undocumented.rst b/tests/examples/sphinx/source/example_no_undocumented.rst index 1b3615f..25f4d8b 100644 --- a/tests/examples/sphinx/source/example_no_undocumented.rst +++ b/tests/examples/sphinx/source/example_no_undocumented.rst @@ -93,6 +93,21 @@ examples.example :type: str +.. data:: MyStringCache + + This is an example of a cache variable. + This variable is a string variable. + + Cache help text: + "Some string a user can set in cache." + + :FORCE: False + + :Default value: String + + :type: STRING + + .. function:: message("hello") diff --git a/tests/examples/sphinx/source/example_no_undocumented_diff_header.rst b/tests/examples/sphinx/source/example_no_undocumented_diff_header.rst index 9bdbb86..e57f177 100644 --- a/tests/examples/sphinx/source/example_no_undocumented_diff_header.rst +++ b/tests/examples/sphinx/source/example_no_undocumented_diff_header.rst @@ -93,6 +93,21 @@ examples.example.cmake :type: str +.. data:: MyStringCache + + This is an example of a cache variable. + This variable is a string variable. + + Cache help text: + "Some string a user can set in cache." + + :FORCE: False + + :Default value: String + + :type: STRING + + .. function:: message("hello") diff --git a/tests/examples/sphinx/source/example_prefix.rst b/tests/examples/sphinx/source/example_prefix.rst index 0ec5a92..e1ca146 100644 --- a/tests/examples/sphinx/source/example_prefix.rst +++ b/tests/examples/sphinx/source/example_prefix.rst @@ -93,6 +93,21 @@ prefix.example :type: str +.. data:: MyStringCache + + This is an example of a cache variable. + This variable is a string variable. + + Cache help text: + "Some string a user can set in cache." + + :FORCE: False + + :Default value: String + + :type: STRING + + .. function:: message("hello") diff --git a/tests/unit_tests/test_documenter.py b/tests/unit_tests/test_documenter.py index f8c403f..33299d6 100755 --- a/tests/unit_tests/test_documenter.py +++ b/tests/unit_tests/test_documenter.py @@ -74,7 +74,7 @@ def test_process(self): self.fail(f"Unknown documentation type: {doc}") def test_incorrect_variable_type(self): - var_doc = VariableDocumentation("name", "Not a valid variable type", "0", "This should fail") + var_doc = VariableDocumentation("name", "Not a valid variable type", "0", "This should fail", {}) self.assertRaises(ValueError, var_doc.process, RSTWriter("test_writer"))