Repository: CMakePP/CMinx

Issue:
Add a regex filter for parameter names to make longer names more readable
**Is your feature request related to a problem? Please describe.**
A regex filter was requested in discussions for https://github.com/CMakePP/CMakeTest/issues/5


Discussion:
codecov[bot]: ## [Codecov](https://codecov.io/gh/CMakePP/CMinx/pull/151?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP) Report
Patch coverage: **`100.00`**% and no project coverage change.
> Comparison is base [(`e51acd0`)](https://codecov.io/gh/CMakePP/CMinx/commit/e51acd0bc52fbd974a9ff1931c10f42403e0a0f8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP) 97.09% compared to head [(`cf8f647`)](https://codecov.io/gh/CMakePP/CMinx/pull/151?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP) 97.10%.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master     #151   +/-   ##
=======================================
  Coverage   97.09%   97.10%           
=======================================
  Files           8        8           
  Lines         930      932    +2     
=======================================
+ Hits          903      905    +2     
  Misses         27       27           
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `97.10% <100.00%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/CMakePP/CMinx/pull/151?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP) | Coverage Δ | |
|---|---|---|
| [src/cminx/aggregator.py](https://codecov.io/gh/CMakePP/CMinx/pull/151?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP#diff-c3JjL2NtaW54L2FnZ3JlZ2F0b3IucHk=) | `95.27% <100.00%> (+0.01%)` | :arrow_up: |
| [src/cminx/config.py](https://codecov.io/gh/CMakePP/CMinx/pull/151?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP#diff-c3JjL2NtaW54L2NvbmZpZy5weQ==) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP)

</details>

[:umbrella: View full report in Codecov by Sentry](https://codecov.io/gh/CMakePP/CMinx/pull/151?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CMakePP).


Patch:
diff --git a/docs/source/developer/api.rst b/docs/source/developer/api.rst
index a397b37..e836a7e 100644
--- a/docs/source/developer/api.rst
+++ b/docs/source/developer/api.rst
@@ -25,10 +25,12 @@ Python Modules
 .. autosummary::
    :toctree: .autosummary
 
-
-   cminx.rstwriter
-   cminx.documenter
    cminx.aggregator
+   cminx.config
+   cminx.documenter
+   cminx.documentation_types
+   cminx.exceptions
+   cminx.rstwriter
    cminx.parser.CMakeLexer
    cminx.parser.CMakeParser
    cminx.parser.CMakeListener
diff --git a/src/cminx/aggregator.py b/src/cminx/aggregator.py
index a7861d3..71d43e0 100755
--- a/src/cminx/aggregator.py
+++ b/src/cminx/aggregator.py
@@ -12,13 +12,30 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+"""
+This module interfaces with the generated CMake parser.
+The primary entrypoint is :class:`~DocumentationAggregator`,
+which subclasses :class:`cminx.parser.CMakeListener`. This
+class listens to all relevent parser rules, generates
+:class:`cminx.documentation_types.DocumentationType`
+objects that represent the parsed CMake, and
+aggregates them in a single list.
+
+:Author: Branden Butler
+:License: Apache 2.0
+"""
+
 import logging
-from typing import List
+import re
+from dataclasses import dataclass
+from typing import List, Union
+
+from antlr4 import ParserRuleContext
 
 from .documentation_types import AttributeDocumentation, FunctionDocumentation, MacroDocumentation, \
     VariableDocumentation, GenericCommandDocumentation, ClassDocumentation, TestDocumentation, SectionDocumentation, \
     MethodDocumentation, VarType, CTestDocumentation, ModuleDocumentation, AbstractCommandDefinitionDocumentation, \
-    OptionDocumentation, DanglingDoccomment
+    OptionDocumentation, DanglingDoccomment, DocumentationType
 
 from .exceptions import CMakeSyntaxException
 from .parser.CMakeListener import CMakeListener
@@ -27,50 +44,64 @@ from .parser.CMakeListener import CMakeListener
 from .parser.CMakeParser import CMakeParser
 from cminx import Settings
 
-"""
-This module interfaces with the generated CMake parser.
-It also subclasses CMakeListener to aggregate and further
-process documented commands on-the-fly.
-
-Processed documentation is stored in two different types of named tuples
-depending on the type being documented. VariableDocumentation also stores
-what type the variable is, either String or List for most purposes but also
-supports Unset in case someone wants to document why something is unset.
-
-:Author: Branden Butler
-:License: Apache 2.0
-"""
-
 
+@dataclass
 class DefinitionCommand:
-    def __init__(self, documentation: AbstractCommandDefinitionDocumentation, should_document=True):
-        self.documentation = documentation
-        self.should_document = should_document
+    """
+    A container for documentation of definition commands, used to update
+    the parameter list when a :code:`cmake_parse_arguments()` command
+    is encountered.
+
+    A definition command is a command that defines another command,
+    so the function() and macro() commands are definition commands.
+    Instances of this dataclass are placed on the top of a stack
+    when definition commands are encountered.
+    """
+    documentation: Union[AbstractCommandDefinitionDocumentation, None]
+    """
+    The documentation for the definition command. If None,
+    this DefinitionCommand will be ignored when popped.
+    """
+
+    should_document: bool = True
+    """
+    Whether the contained documentation object should be updated
+    if a :code:`cmake_parse_arguments()` command is found.
+    When false, this object is ignored.
+    """
 
 
 class DocumentationAggregator(CMakeListener):
     """
     Processes all docstrings and their associated commands, aggregating
-    them in a list.
+    them in a list. Uses the given :class:`~cminx.config.Settings` object
+    to determine aggregation settings, and will document commands without
+    doccomments if the associated settings are set.
+
+    The method used to generate the documentation object for
+    a given command is chosen by searching for a method with the name
+    :code:`"process_<command name>"` with two arguments: :code:`ctx` that is
+    of type :class:`cminx.parser.CMakeParser.Command_invocationContext`,
+    and :code:`docstring` that is of type string.
     """
 
-    def __init__(self, settings: Settings = Settings()):
-        self.settings = settings
+    def __init__(self, settings: Settings = Settings()) -> None:
+        self.settings: Settings = settings
         """Application settings used to determine what commands to document"""
 
-        self.documented = []
+        self.documented: List[DocumentationType] = []
         """All current documented commands"""
 
-        self.documented_classes_stack = []
+        self.documented_classes_stack: List[Union[ClassDocumentation, None]] = []
         """A stack containing the documented classes and inner classes as they are processed"""
 
-        self.documented_awaiting_function_def = None
+        self.documented_awaiting_function_def: Union[DocumentationType, None] = None
         """
         A variable containing a documented command such as cpp_member() that is awaiting its function/macro
         definition
         """
 
-        self.definition_command_stack = []
+        self.definition_command_stack: List[DefinitionCommand] = []
         """
         A stack containing the current definition command that we are inside.
         A definition command is any command that defines a construct with both a
@@ -79,14 +110,14 @@ class DocumentationAggregator(CMakeListener):
         function(), macro(), and cpp_class().
         """
 
-        self.consumed = []
+        self.consumed: List[ParserRuleContext] = []
         """
         A list containing all of the contexts that have already been processed
         """
 
-        self.logger = logging.getLogger(__name__)
+        self.logger: logging.Logger = logging.getLogger(__name__)
 
-    def process_function(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_function(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Extracts function name and declared parameters.
 
@@ -106,7 +137,9 @@ class DocumentationAggregator(CMakeListener):
                 ctx.start.line
             )
 
-        params = [p.getText() for p in def_params[1:]]
+        params = [
+            re.sub(self.settings.input.function_parameter_name_strip_regex, "", p.getText()) for p in def_params[1:]
+        ]
         function_name = def_params[0].getText()
         has_kwargs = self.settings.input.kwargs_doc_trigger_string in docstring
 
@@ -115,7 +148,7 @@ class DocumentationAggregator(CMakeListener):
         self.documented.append(doc)
         self.definition_command_stack.append(DefinitionCommand(doc))
 
-    def process_macro(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_macro(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Extracts macro name and declared parameters.
 
@@ -135,7 +168,9 @@ class DocumentationAggregator(CMakeListener):
                 ctx.start.line
             )
 
-        params = [p.getText() for p in def_params[1:]]
+        params = [
+            re.sub(self.settings.input.macro_parameter_name_strip_regex, "", p.getText()) for p in def_params[1:]
+        ]
         macro_name = def_params[0].getText()
         has_kwargs = self.settings.input.kwargs_doc_trigger_string in docstring
 
@@ -144,9 +179,11 @@ class DocumentationAggregator(CMakeListener):
         self.documented.append(doc)
         self.definition_command_stack.append(DefinitionCommand(doc))
 
-    def process_cmake_parse_arguments(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_cmake_parse_arguments(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Determines whether a documented function or macro uses *args or *kwargs.
+        Accesses the last element in the :code:`definition_command_stack` to
+        update the documentation's :code:`has_kwargs` field.
 
         :param ctx: Documented command context. Constructed by the Antlr4 parser.
 
@@ -158,7 +195,7 @@ class DocumentationAggregator(CMakeListener):
                                                            AbstractCommandDefinitionDocumentation):
                 last_element.documentation.has_kwargs = True
 
-    def process_ct_add_test(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_ct_add_test(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Extracts test name and declared parameters.
 
@@ -198,7 +235,7 @@ class DocumentationAggregator(CMakeListener):
         self.documented.append(test_doc)
         self.documented_awaiting_function_def = test_doc
 
-    def process_ct_add_section(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_ct_add_section(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Extracts section name and declared parameters.
 
@@ -237,7 +274,7 @@ class DocumentationAggregator(CMakeListener):
         self.documented.append(section_doc)
         self.documented_awaiting_function_def = section_doc
 
-    def process_set(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_set(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Extracts variable name and values from the documented set command.
         Also determines the type of set command/variable: String, List, or Unset.
@@ -280,7 +317,7 @@ class DocumentationAggregator(CMakeListener):
             self.documented.append(VariableDocumentation(
                 varname, docstring, VarType.UNSET, None))
 
-    def process_cpp_class(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_cpp_class(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Extracts the name and the declared superclasses from the documented
         cpp_class command.
@@ -315,7 +352,7 @@ class DocumentationAggregator(CMakeListener):
         self.documented_classes_stack.append(clazz)
 
     def process_cpp_member(self, ctx: CMakeParser.Command_invocationContext, docstring: str,
-                           is_constructor: bool = False):
+                           is_constructor: bool = False) -> None:
         """
         Extracts the method name and declared parameter types from the documented cpp_member
         command.
@@ -362,13 +399,13 @@ class DocumentationAggregator(CMakeListener):
             clazz.members.append(method_doc)
         self.documented_awaiting_function_def = method_doc
 
-    def process_cpp_constructor(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_cpp_constructor(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Alias for calling process_cpp_member() with is_constructor=True.
         """
         self.process_cpp_member(ctx, docstring, is_constructor=True)
 
-    def process_cpp_attr(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_cpp_attr(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Extracts the name and any default values from the documented cpp_attr
         command.
@@ -405,7 +442,7 @@ class DocumentationAggregator(CMakeListener):
         clazz.attributes.append(AttributeDocumentation(
             name, docstring, parent_class, default_values))
 
-    def process_add_test(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_add_test(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Extracts information from a CTest add_test() command.
         Note: this is not the processor for the CMakeTest ct_add_test() command,
@@ -441,7 +478,7 @@ class DocumentationAggregator(CMakeListener):
         test_doc = CTestDocumentation(name, docstring, [p for p in params if p != name and p != "NAME"])
         self.documented.append(test_doc)
 
-    def process_option(self, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_option(self, ctx: CMakeParser.Command_invocationContext, docstring: str) -> None:
         """
         Extracts information from an :code:`option()` command and creates
         an OptionDocumentation from it. It extracts the option name,
@@ -467,7 +504,8 @@ class DocumentationAggregator(CMakeListener):
         )
         self.documented.append(option_doc)
 
-    def process_generic_command(self, command_name: str, ctx: CMakeParser.Command_invocationContext, docstring: str):
+    def process_generic_command(self, command_name: str, ctx: CMakeParser.Command_invocationContext,
+                                docstring: str) -> None:
         """
         Extracts command invocation and arguments for a documented command that does not
         have a dedicated processor function.
@@ -512,7 +550,7 @@ class DocumentationAggregator(CMakeListener):
 
         return cleaned_doc
 
-    def enterDocumented_command(self, ctx: CMakeParser.Documented_commandContext):
+    def enterDocumented_command(self, ctx: CMakeParser.Documented_commandContext) -> None:
         """
         Main entrypoint into the documentation processor and aggregator. Called by ParseTreeWalker whenever
         encountering a documented command. Cleans the docstring and dispatches ctx to other functions for additional
@@ -543,7 +581,7 @@ class DocumentationAggregator(CMakeListener):
             )
             raise e
 
-    def enterCommand_invocation(self, ctx: CMakeParser.Command_invocationContext):
+    def enterCommand_invocation(self, ctx: CMakeParser.Command_invocationContext) -> None:
         """
         Visitor for all other commands, used for locating position-dependent
         elements of documented commands, such as cpp_end_class() that pops the class stack,
@@ -566,6 +604,11 @@ class DocumentationAggregator(CMakeListener):
                 # We've found the function/macro def that the previous documented command needed
                 params = [param.getText()
                           for param in ctx.single_argument()]
+                if isinstance(self.documented_awaiting_function_def, MethodDocumentation):
+                    params = [
+                        re.sub(self.settings.input.member_parameter_name_strip_regex, "", p.getText()) for p in
+                        ctx.single_argument()
+                    ]
 
                 self.documented_awaiting_function_def.is_macro = command == "macro"
 
@@ -592,18 +635,18 @@ class DocumentationAggregator(CMakeListener):
             self.logger.error(f"Caught exception while processing command beginning at line number {line_num}")
             raise e
 
-    def enterDocumented_module(self, ctx: CMakeParser.Documented_moduleContext):
+    def enterDocumented_module(self, ctx: CMakeParser.Documented_moduleContext) -> None:
         text = ctx.Module_docstring().getText()
         cleaned_lines = DocumentationAggregator.clean_doc_lines(text.split("\n")).split("\n")
         module_name = cleaned_lines[0].replace("@module", "").strip()
         doc = "\n".join(cleaned_lines[1:])
         self.documented.append(ModuleDocumentation(module_name, doc))
 
-    def enterBracket_doccomment(self, ctx: CMakeParser.Bracket_doccommentContext):
+    def enterBracket_doccomment(self, ctx: CMakeParser.Bracket_doccommentContext) -> None:
         if ctx not in self.consumed:
-            text = ctx.Docstring().getText()
-            cleaned_lines = DocumentationAggregator.clean_doc_lines(text.split("\n")).split("\n")
-            #self.documented.append(DanglingDoccomment("", "\n".join(cleaned_lines)))
+            # text = ctx.Docstring().getText()
+            # cleaned_lines = DocumentationAggregator.clean_doc_lines(text.split("\n")).split("\n")
+            # self.documented.append(DanglingDoccomment("", "\n".join(cleaned_lines)))
             self.logger.warning(
                 f"Detected dangling doccomment, ignoring. RST may change depending on CMinx version."
             )
diff --git a/src/cminx/config.py b/src/cminx/config.py
index cb9b1f3..fd42168 100644
--- a/src/cminx/config.py
+++ b/src/cminx/config.py
@@ -12,6 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+"""
+Contains config objects, their defaults,
+the config template for confuse, and a function
+to convert a dictionary to a :class:`~Settings` object.
+
+The settings object mirrors the template and
+the :code:`config_default.yaml` file. Visit that file
+for information on what each setting does.
+"""
+
 import confuse
 import os
 from typing import List
@@ -40,6 +50,9 @@ def config_template(output_dir_relative_to_config: bool = False) -> dict:
             "auto_exclude_directories_without_cmake": bool,
             "kwargs_doc_trigger_string": confuse.Optional(confuse.String(), default=":keyword"),
             "exclude_filters": confuse.Optional(list, default=()),
+            "function_parameter_name_strip_regex": confuse.Optional(confuse.String(), default=""),
+            "macro_parameter_name_strip_regex": confuse.Optional(confuse.String(), default=""),
+            "member_parameter_name_strip_regex": confuse.Optional(confuse.String(), default=""),
             "recursive": bool,
             "follow_symlinks": bool
         },
@@ -76,6 +89,9 @@ class InputSettings:
     auto_exclude_directories_without_cmake: bool = True
     kwargs_doc_trigger_string: str = ":param **kwargs:"
     exclude_filters: List[str] = ()
+    function_parameter_name_strip_regex: str = ""
+    macro_parameter_name_strip_regex: str = ""
+    member_parameter_name_strip_regex: str = ""
     recursive: bool = False
     follow_symlinks: bool = False
 
diff --git a/src/cminx/config_default.yaml b/src/cminx/config_default.yaml
index 0af9d04..f620f81 100644
--- a/src/cminx/config_default.yaml
+++ b/src/cminx/config_default.yaml
@@ -68,6 +68,30 @@ input:
   # body of the function or macro.
   kwargs_doc_trigger_string: ":keyword"
 
+  # Strips out any string matching this regex
+  # for parameter names. This is useful for when
+  # parameter names have a prefix/postfix to prevent
+  # name collisions. This affects function definitions.
+  #
+  # An example regex that strips the prefix "_abc_"
+  # from parameter names where "abc" is any combination of letters
+  # is below:
+  # function_parameter_name_strip_regex: "^_[a-zA-Z]*_"
+  function_parameter_name_strip_regex: ""
+
+  # Strips out any string matching this regex
+  # for parameter names. This is useful for when
+  # parameter names have a prefix/postfix to prevent
+  # name collisions. This affects macro definitions
+  macro_parameter_name_strip_regex: ""
+
+  # Strips out any string matching this regex
+  # for parameter names. This is useful for when
+  # parameter names have a prefix/postfix to prevent
+  # name collisions. This affects CMakePP
+  # class member definitions.
+  member_parameter_name_strip_regex: ""
+
   # Whether directories should be documented recursively
   recursive: false
 
diff --git a/src/cminx/documentation_types.py b/src/cminx/documentation_types.py
index 399fc90..d202646 100644
--- a/src/cminx/documentation_types.py
+++ b/src/cminx/documentation_types.py
@@ -23,13 +23,12 @@ representation into an RST representation using :class:`RSTWriter`.
 :License: Apache 2.0
 
 """
-
+import textwrap
 from abc import ABC, abstractmethod
 from dataclasses import dataclass, field
 from enum import Enum
 from typing import Union, List
 
-from . import Settings
 from .rstwriter import RSTWriter, Directive, interpreted_text
 
 
@@ -37,8 +36,16 @@ class VarType(Enum):
     """The types of variables accepted by the CMake :code:`set()` command"""
     
     STRING = 1
+    """A String variable, ex: :code:`set(str_var "Hello")`"""
+
     LIST = 2
+    """A List variable, ex: :code:`set(list_var item1 item2 item3)`"""
+
     UNSET = 3
+    """
+    Unsetting a variable, i.e. not an actual variable but a declaration
+    that a variable no longer exists. Ex: :code:`set(MyVar)`
+    """
 
 
 @dataclass
@@ -56,13 +63,13 @@ class DocumentationType(ABC):
     """The full doc comment, cleaned of # characters."""
 
     @abstractmethod
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         """
         Processes the data stored in this documentation type,
         converting it to RST form by using the given RST writer.
 
         :param writer: RSTWriter instance that the documentation
-        will be added to.
+                       will be added to.
         """
         pass
 
@@ -89,7 +96,7 @@ class FunctionDocumentation(AbstractCommandDefinitionDocumentation):
     directly to a Sphinx :code:`function` directive.
     """
 
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         param_list = self.params
         if self.has_kwargs:
             param_list.append("**kwargs")
@@ -107,7 +114,7 @@ class MacroDocumentation(AbstractCommandDefinitionDocumentation):
     :code:`note` directive specifying that it is a macro.
     """
 
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         param_list = self.params
         if self.has_kwargs:
             param_list.append("**kwargs")
@@ -125,7 +132,7 @@ class VariableDocumentation(DocumentationType):
     This class serves as the representation of a documented
     CMake variable definition (:code:`set()` command). It can only
     recognize the CMake primitives :code:`str` and :code:`list`, as
-    well as the :code:`UNSET` subcommand.
+    well as the implied :code:`unset()` when no value is given.
 
     This class translates the definition into a Sphinx
     :code:`data` directive, with a :code:`Default value` field
@@ -138,7 +145,7 @@ class VariableDocumentation(DocumentationType):
     value: Union[str, None]
     """A default value that the variable has"""
 
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         d = writer.directive("data", f"{self.name}")
         d.text(self.doc)
         d.field("Default value", self.value)
@@ -166,15 +173,15 @@ class OptionDocumentation(VariableDocumentation):
     help_text: str
     """The help text that the option has."""
 
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         d = writer.directive("data", f"{self.name}")
         note = d.directive("note")
-        note.text(
+        note.text(textwrap.dedent(
             f"""
             This variable is a user-editable option,
             meaning it appears within the cache and can be
             edited on the command line by the :code:`-D` flag.
-            """)
+            """))
         d.text(self.doc)
         d.field("Help text", self.help_text)
         d.field("Default value", self.value if self.value is not None else "OFF")
@@ -196,7 +203,7 @@ class GenericCommandDocumentation(DocumentationType):
     params: List[str]
     """Any parameters passed to the command, unfiltered since this documentation type has no knowledge of the command"""
 
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         d = writer.directive(
             "function", f"{self.name}({' '.join(self.params)})")
         d.directive(
@@ -216,7 +223,7 @@ class CTestDocumentation(DocumentationType):
     """
     params: List[str] = field(default_factory=lambda: [])
 
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         d = writer.directive(
             "function",
             f"{self.name}({' '.join(self.params)})")
@@ -247,7 +254,7 @@ class TestDocumentation(DocumentationType):
     is_macro: bool = False
     """Whether the linked command is a macro or a function. If true, a warning stating so is generated."""
 
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         d = writer.directive(
             "function",
             f"{self.name}({'EXPECTFAIL' if self.expect_fail else ''})")
@@ -267,7 +274,7 @@ class SectionDocumentation(TestDocumentation):
     section.
     """
 
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         d = writer.directive(
             "function",
             f"{self.name}({'EXPECTFAIL' if self.expect_fail else ''})")
@@ -305,7 +312,7 @@ class MethodDocumentation(DocumentationType):
     is_macro: bool = False
     """Whether the linked command is a macro or a function. If true, a note saying so is generated."""
 
-    def process(self, writer: Directive):
+    def process(self, writer: Directive) -> None:
         params_pretty = ', '.join(
             self.params) + ("[, ...]" if "args" in self.param_types else "")
         d = writer.directive(
@@ -344,7 +351,7 @@ class AttributeDocumentation(DocumentationType):
     default_value: str
     """The default value of this attribute, if it has one."""
 
-    def process(self, writer: Directive):
+    def process(self, writer: Directive) -> None:
         d = writer.directive("py:attribute", f"{self.name}")
         if self.default_value is not None:
             d.option("value", self.default_value)
@@ -384,7 +391,7 @@ class ClassDocumentation(DocumentationType):
     attributes: List[AttributeDocumentation]
     """A list of attribute documentation objects describing the class's attributes."""
 
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         d = writer.directive("py:class", f"{self.name}")
         if len(self.superclasses) > 0:
             bases = "Bases: " + \
@@ -419,7 +426,7 @@ class ModuleDocumentation(DocumentationType):
     Represents documentation for an entire CMake module
     """
 
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         module = writer.directive("module", self.name)
         if self.doc is not None and len(self.doc) != 0:
             module.text(self.doc)
@@ -432,5 +439,5 @@ class DanglingDoccomment(DocumentationType):
     This documentation type's name is left unused, and the doc contains
     the cleaned text of the dangling doccomment.
     """
-    def process(self, writer: RSTWriter):
+    def process(self, writer: RSTWriter) -> None:
         writer.text(self.doc)
diff --git a/src/cminx/documenter.py b/src/cminx/documenter.py
index d653bd8..8826a16 100755
--- a/src/cminx/documenter.py
+++ b/src/cminx/documenter.py
@@ -13,9 +13,9 @@
 # limitations under the License.
 #
 """
-This file contains the Documenter class, which combines functionality
-from parser.aggregator and rstwriter to generate RST documentation
-for CMake files.
+This file contains the Documenter class, which is the top-level
+entrypoint into the CMinx documentation system. It handles parsing,
+aggregating, and RST writing.
 
 :Author: Branden Butler
 :License: Apache 2.0
@@ -36,7 +36,9 @@ from .rstwriter import RSTWriter, Directive
 
 class Documenter(object):
     """
-    Generates RST documentation from aggregated documentation, combining parser.aggregator and rstwriter.
+    Generates RST documentation from aggregated documentation, combining
+    :class:`~cminx.aggregator.DocumentationAggregator` and :class:`~cminx.rstwriter.RSTWriter`.
+    The entrypoint for this class is the :meth:`process` method.
     """
 
     def __init__(
@@ -44,48 +46,67 @@ class Documenter(object):
             file: str,
             title: str = None,
             module_name: str = None,
-            settings: Settings = Settings()):
+            settings: Settings = Settings()) -> None:
         """
         :param file: CMake file to read documentation from.
         :param title: RST header title to use in the generated document.
+        :param module_name: The name of the CMake module, used in the module directive if
+                            no other name is given in a module doccomment.
         :param settings: Dictionary containing application settings for documentation
         """
 
-        self.settings = settings
+        self.settings: Settings = settings
+        """Settings used for aggregation and RST generation, passed down to all downstream components."""
 
         title = file if title is None else title
 
         if module_name is None:
             module_name = title
 
-        self.module_name = module_name
+        self.module_name: str = module_name
+        """The name of the CMake module, used as a default when no name given via a module doccomment."""
 
-        self.writer = RSTWriter(title, settings=settings)
+        self.writer: RSTWriter = RSTWriter(title, settings=settings)
+        """The writer that is passed to the aggregated documentation objects."""
 
         self.module: Directive
+        """The :code:`.. module::` directive that defines the module's name."""
 
         # We need a string stream of some kind, FileStream is easiest
-        self.input_stream = FileStream(file)
+        self.input_stream: InputStream = FileStream(file)
+        """The string stream used to read the CMake file."""
 
         # Convert those strings into tokens and build a stream from those
-        self.lexer = CMakeLexer(self.input_stream)
-        self.stream = CommonTokenStream(self.lexer)
+        self.lexer: CMakeLexer = CMakeLexer(self.input_stream)
+        """The lexer used to generate the token stream."""
+
+        self.stream: TokenStream = CommonTokenStream(self.lexer)
+        """The stream of tokens from the lexer, should be passed to the parser."""
 
         # We now have a stream of CommonToken instead of strings, parsers
         # require this type of stream
-        self.parser = CMakeParser(self.stream)
+        self.parser: CMakeParser = CMakeParser(self.stream)
+        """
+        The parser used to parse the token stream and call our listener.
+        By default, an instance of :class:`~cminx.parser.ParserErrorListener` is
+        added.
+        """
+
         self.parser.addErrorListener(ParserErrorListener())
 
         # Hard part is done, we now have a fully usable parse tree, now we just
         # need to walk it
-        self.aggregator = DocumentationAggregator(settings)
-        self.walker = ParseTreeWalker()
+        self.aggregator: DocumentationAggregator = DocumentationAggregator(settings)
+        """The aggregator used to listen for parser rules and generate documentation objects."""
+
+        self.walker: ParseTreeWalker = ParseTreeWalker()
+        """Walks the parser tree at a given parser rule, used to kick off aggregation."""
 
     def process(self) -> RSTWriter:
         """
-        Process Documenter.aggregator.documented and build RST document from it.
+        Process :attr:`cminx.aggregator.DocumentationAggregator.documented` and build RST document from it.
 
-        :return: Completed RSTWriter document, also located in Documenter.writer
+        :return: Completed RSTWriter document, also located in :attr:`Documenter.writer`
         """
 
         # Parse and lex the file, then walk the tree and aggregate the
@@ -99,9 +120,10 @@ class Documenter(object):
         self.process_docs(self.aggregator.documented)
         return self.writer
 
-    def process_docs(self, docs: List[DocumentationType]):
+    def process_docs(self, docs: List[DocumentationType]) -> None:
         """
-        Loops over document and calls Documentation.process() with self.writer
+        Loops over document and calls :meth:`cminx.documentation_types.DocumentationType.process` with
+        :attr:`~Documenter.writer`.
 
         :param docs: List of documentation objects.
         """
diff --git a/src/cminx/exceptions.py b/src/cminx/exceptions.py
index e9424c7..2dd54e0 100644
--- a/src/cminx/exceptions.py
+++ b/src/cminx/exceptions.py
@@ -17,5 +17,20 @@ import dataclasses
 
 @dataclasses.dataclass
 class CMakeSyntaxException(Exception):
+    """
+    Denotes a syntax error in the CMake source code.
+    This is usually not recoverable and analysis
+    of the CMake code is not guaranteed to be consistent,
+    so generation of this exception is expected to
+    halt the program.
+    """
+
     msg: str
+    """A message describing the syntax error."""
+
     line: int
+    """
+    The line in the CMake file where the exception was detected.
+    Note that this may not be the line where the error is actually located
+    in the source, only where the lexer or parser detected an error.
+    """
diff --git a/src/cminx/rstwriter.py b/src/cminx/rstwriter.py
index 375534c..febadad 100755
--- a/src/cminx/rstwriter.py
+++ b/src/cminx/rstwriter.py
@@ -31,8 +31,19 @@ from cminx import Settings
 
 
 class ListType(Enum):
+    """
+    Represents the different types of RST
+    lists.
+    """
+
     ENUMERATED = 0
+    """
+    An enumerated RST list.
+    """
     BULLETED = 1
+    """
+    A bulleted RST list. 
+    """
 
 
 def interpreted_text(role: str, text: str) -> str:
@@ -55,13 +66,13 @@ class Paragraph(object):
     Represents an RST paragraph
     """
 
-    def __init__(self, text: str, indent: str = ""):
-        self.text = text
-        self.prefix = indent
-        self.text_string = ""
+    def __init__(self, text: str, indent: str = "") -> None:
+        self.text: str = text
+        self.prefix: str = indent
+        self.text_string: str = ""
         self.build_text_string()
 
-    def build_text_string(self):
+    def build_text_string(self) -> None:
         """
         Populates Paragraph.text_string with the
         RST string corresponding to this paragraph
@@ -69,7 +80,7 @@ class Paragraph(object):
         self.text_string = "\n".join(
             [self.prefix + text for text in self.text.split("\n")])
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.text_string
 
 
@@ -78,14 +89,14 @@ class Field(object):
     Represents an RST field, such as Author
     """
 
-    def __init__(self, field_name: str, field_text: str, indent: str = ""):
-        self.field_name = field_name
-        self.field_text = field_text
-        self.field_string = ""
-        self.indent = indent
+    def __init__(self, field_name: str, field_text: str, indent: str = "") -> None:
+        self.field_name: str = field_name
+        self.field_text: str = field_text
+        self.field_string: str = ""
+        self.indent: str = indent
         self.build_field_string()
 
-    def build_field_string(self):
+    def build_field_string(self) -> None:
         """
         Populates Field.field_string with the
         RST string corresponding to this field
@@ -93,7 +104,7 @@ class Field(object):
 
         self.field_string = f"\n{self.indent}:{self.field_name}: {self.field_text}"
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.field_string
 
 
@@ -102,21 +113,21 @@ class DocTest(object):
     Represents an RST DocTest
     """
 
-    def __init__(self, test_line: str, expected_output: str, indent=""):
-        self.test_line = test_line
-        self.expected_output = expected_output
-        self.doctest_string = ""
-        self.indent = indent
+    def __init__(self, test_line: str, expected_output: str, indent: str = "") -> None:
+        self.test_line: str = test_line
+        self.expected_output: str = expected_output
+        self.doctest_string: str = ""
+        self.indent: str = indent
         self.build_doctest_string()
 
-    def build_doctest_string(self):
+    def build_doctest_string(self) -> None:
         """
         Populates DocTest.doctest_string with the
         RST string corresponding to this DocTest
         """
         self.doctest_string = f"\n{self.indent}>>> {self.test_line}\n{self.expected_output}\n"
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.doctest_string
 
 
@@ -126,17 +137,19 @@ class RSTList(object):
     Enumerated or Bulleted
     """
 
-    def __init__(self, items: Tuple[str], list_type: ListType, indent: str = ""):
-        self.items = items
-        self.list_type = list_type
-        self.list_string = ""
-        self.indent = indent
+    def __init__(self, items: Tuple[str], list_type: ListType, indent: str = "") -> None:
+        self.items: Tuple[str] = items
+        self.list_type: ListType = list_type
+        self.list_string: str = ""
+        self.indent: str = indent
         self.build_list_string()
 
-    def build_list_string(self):
+    def build_list_string(self) -> None:
         """
         Populates RSTList.list_string with the
-        RST string corresponding to this list
+        RST string corresponding to this list.
+
+        :raises ValueError: When :py:attr:`~.RSTList.list_type` is unknown.
         """
 
         self.list_string = "\n"
@@ -149,7 +162,7 @@ class RSTList(object):
         else:
             raise ValueError("Unknown list type")
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.list_string
 
 
@@ -158,13 +171,13 @@ class Heading(object):
     Represents a section heading
     """
 
-    def __init__(self, title: str, header_char: str):
+    def __init__(self, title: str, header_char: str) -> None:
         self.title = title
         self.header_char = header_char
         self.heading_string = ""
         self.build_heading_string()
 
-    def build_heading_string(self):
+    def build_heading_string(self) -> None:
         """
         Populates Heading.heading_string with the
         RST string corresponding to this heading
@@ -174,7 +187,7 @@ class Heading(object):
             heading += self.header_char
         self.heading_string = f"\n{heading}\n{self.title}\n{heading}"
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.heading_string
 
 
@@ -184,12 +197,12 @@ class SimpleTable(object):
     """
 
     def __init__(self, tab: List[List[str]], headings: List[str]):
-        self.table = tab
-        self.column_headings = headings
-        self.table_string = ""
+        self.table: List[List[str]] = tab
+        self.column_headings: List[str] = headings
+        self.table_string: str = ""
         self.build_table_string()
 
-    def build_table_string(self):
+    def build_table_string(self) -> None:
         """
         Populates SimpleTable.table_string with the
         RST string equivalent of this table
@@ -212,7 +225,7 @@ class SimpleTable(object):
             raise ValueError(
                 "Different number of headings than number of columns in table")
 
-        # Find the longest cell to use as the row separator width Yes I know doing it this way is inefficient,
+        # Find the longest cell to use as the row separator width. Yes I know doing it this way is inefficient,
         # but readability is more important unless we're dealing with thousands
         # of cells.
         for row in self.table:
@@ -236,27 +249,25 @@ class SimpleTable(object):
             row_separator += "="
 
         table_str = ""
-        # Index zero is heading overline, index 1 is headings, index 2 is
-        # underline
-        heading_lines = ["", "", ""]
+        heading_lines = {"overline": "", "headings": "", "underline": ""}
         # Build heading overlines/underlines and add correct spacing
         for heading in self.column_headings:
             heading_len = len(heading)
             for i in range(0, row_separator_width):
-                heading_lines[0] += '='
+                heading_lines["overline"] += '='
 
                 # If current line position is not greater than the length of the heading, add the character at position
                 # Else, add spaces until end of row separator
                 if i < heading_len:
-                    heading_lines[1] += heading[i]
+                    heading_lines["headings"] += heading[i]
                 else:
-                    heading_lines[1] += " "
-            heading_lines[0] += "  "  # Recommended 2 spaces between columns
-            heading_lines[1] += "  "
+                    heading_lines["headings"] += " "
+            heading_lines["overline"] += "  "  # Recommended 2 spaces between columns
+            heading_lines["headings"] += "  "
 
         # Overline and underline should be the same
-        heading_lines[2] = heading_lines[0]
-        table_str += "\n".join(heading_lines) + "\n"
+        heading_lines["underline"] = heading_lines["overline"]
+        table_str += "\n".join(heading_lines.values()) + "\n"
 
         # Add cells to table string
         for row in self.table:
@@ -272,7 +283,7 @@ class SimpleTable(object):
         table_str += "\n"
         self.table_string = table_str
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.table_string
 
 
@@ -281,17 +292,18 @@ class DirectiveHeading(object):
     Represents the unique heading for a Directive (.. :<name>:)
     """
 
-    def __init__(self, title: str, indent: str, args: str):
-        self.title = title
-        self.indent = indent
-        self.args = args
-        self.heading_string = ""
+    def __init__(self, title: str, indent: str, args: str) -> None:
+        self.title: str = title
+        self.indent: str = indent
+        self.args: str = args
+        self.heading_string: str = ""
         self.build_heading_string()
 
-    def build_heading_string(self):
+    def build_heading_string(self) -> None:
+        """Builds the directive heading string and stores in :py:attr:`~heading_str`"""
         self.heading_string = f"\n{self.indent}.. {self.title}:: {self.args}"
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.heading_string
 
 
@@ -300,29 +312,34 @@ class Option(object):
     Represents a directive option, such as maxdepth
     """
 
-    def __init__(self, name: str, value: str, indent: str):
-        self.name = name
-        self.value = value
-        self.indent = indent
-        self.option_string = ""
+    def __init__(self, name: str, value: str, indent: str) -> None:
+        self.name: str = name
+        self.value: str = value
+        self.indent: str = indent
+        self.option_string: str = ""
         self.build_option_string()
 
-    def build_option_string(self):
+    def build_option_string(self) -> None:
+        """Builds the option string and stores in :py:attr:`~option_string`"""
         self.option_string = f"{self.indent}:{self.name}: {self.value}"
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.option_string
 
 
-def get_indents(num) -> str:
+def get_indents(num: int) -> str:
     """
     Get the string containing the necessary indent.
+    The number of spaces in the returned string equals three times
+    the input number, because directives require three spaces
+    so the text lines up with the first letter of the directive name.
 
-    :return: A string containing the correct number of whitespace characters, derived from the indent level.
+    :return: A string containing the correct number of space characters, derived from the indent level.
     """
     indents = ""
     for i in range(0, num):
-        # Directives require the first non-whitespace character of every line to line up with the first letter of
+        # Directives require the first non-whitespace character
+        # of every line to line up with the first letter of
         # the directive name
         indents += '   '
     return indents
@@ -339,26 +356,26 @@ class RSTWriter(object):
     or by calling :py:func:`str` on this object.
     """
 
-    heading_level_chars = ['#', '*', '=', '-', '_', '~', '!', '&', '@', '^']
+    heading_level_chars: List[str] = ['#', '*', '=', '-', '_', '~', '!', '&', '@', '^']
     """Characters to use as heading overline/underline, indexed by section_level"""
 
     def __init__(
             self,
             title: str,
             section_level: int = 0,
-            settings=Settings(), indent: int = 0):
+            settings: Settings = Settings(), indent: int = 0) -> None:
         self.__title: str = title
         self.section_level: int = section_level
-        self.settings = settings
+        self.settings: Settings = settings
         if settings.rst.headers is not None:
             self.heading_level_chars = settings.rst.headers
 
-        self.indent = indent
+        self.indent: int = indent
         self.header_char: str = self.heading_level_chars[section_level]
         # Heading must be first in the document tree
         self.document: List[Any] = [self.build_heading()]
 
-    def clear(self):
+    def clear(self) -> None:
         """
         Clear all document tree elements (besides required heading)
         """
@@ -369,7 +386,7 @@ class RSTWriter(object):
         return self.__title
 
     @title.setter
-    def title(self, new_title: str):
+    def title(self, new_title: str) -> None:
         """
         Rebuild heading and replace the old one in the document tree whenever title is changed
 
@@ -378,7 +395,7 @@ class RSTWriter(object):
         self.__title = new_title
         self.document[0] = self.build_heading()
 
-    def bulleted_list(self, *items: str):
+    def bulleted_list(self, *items: str) -> None:
         """
         Add a bulleted list to the document tree.
 
@@ -387,7 +404,7 @@ class RSTWriter(object):
 
         self.document.append(RSTList(items, ListType.BULLETED, indent=get_indents(self.indent)))
 
-    def enumerated_list(self, *items: str):
+    def enumerated_list(self, *items: str) -> None:
         """
         Add an enumerated list to the document tree; e.g.
             1. Item 1
@@ -397,7 +414,7 @@ class RSTWriter(object):
         """
         self.document.append(RSTList(items, ListType.ENUMERATED, indent=get_indents(self.indent)))
 
-    def field(self, field_name: str, field_text: str):
+    def field(self, field_name: str, field_text: str) -> None:
         """
         Add a field, such as Author or Version, to the document tree.
 
@@ -407,7 +424,7 @@ class RSTWriter(object):
         """
         self.document.append(Field(field_name, field_text, get_indents(self.indent)))
 
-    def doctest(self, test_line: str, expected_output: str):
+    def doctest(self, test_line: str, expected_output: str) -> None:
         """
         Adds a doctest segment to the document tree.
         A doctest segment appears as an interactive python session.
@@ -433,7 +450,7 @@ class RSTWriter(object):
         self.document.append(sect)
         return sect
 
-    def text(self, txt: str):
+    def text(self, txt: str) -> None:
         """
         Add a paragraph to document tree.
 
@@ -491,7 +508,7 @@ class RSTWriter(object):
             document_string += f"{element}\n"
         return document_string
 
-    def __str__(self):
+    def __str__(self) -> str:
         """
         Equivalent to :func: `~cminx.RSTWriter.to_text`
 
@@ -499,7 +516,7 @@ class RSTWriter(object):
         """
         return self.to_text()
 
-    def write_to_file(self, file: Union[str, IO]):
+    def write_to_file(self, file: Union[str, IO]) -> None:
         """
         Write text representation of this document to file. File may be a string (path will be opened in write mode)
         or a file-like object.
@@ -540,7 +557,7 @@ class Directive(RSTWriter):
     Does not verify that the directive or its arguments are valid.
     """
 
-    def __init__(self, name: str, indent: int = 0, *arguments: str, settings: Settings = Settings()):
+    def __init__(self, name: str, indent: int = 0, *arguments: str, settings: Settings = Settings()) -> None:
         """
         :param name: Name of the directive being constructed, eg. toctree or admonition.
 
@@ -549,8 +566,8 @@ class Directive(RSTWriter):
         :param arguments: Varargs to be used as the directive arguments, eg. a topic's title.
         """
 
-        self.arguments = arguments
-        self.options = []
+        self.arguments: Tuple[str] = arguments
+        self.options: List[Option] = []
         super().__init__(name, settings=settings, indent=indent + 1)
 
     def build_heading(self) -> DirectiveHeading:
@@ -571,7 +588,7 @@ class Directive(RSTWriter):
         """
         return ','.join(map(str, self.arguments))
 
-    def option(self, name: str, value: str = ""):
+    def option(self, name: str, value: str = "") -> None:
         """
         Add an option, such as toctree's maxdepth. Does not verify if valid option
         """


Test patch:
diff --git a/tests/examples/sphinx/source/example.rst b/tests/examples/sphinx/source/example.rst
index 2759669..45fc64b 100644
--- a/tests/examples/sphinx/source/example.rst
+++ b/tests/examples/sphinx/source/example.rst
@@ -346,10 +346,10 @@ examples.example
    .. note:: 
 
       
-                  This variable is a user-editable option,
-                  meaning it appears within the cache and can be
-                  edited on the command line by the :code:`-D` flag.
-                  
+      This variable is a user-editable option,
+      meaning it appears within the cache and can be
+      edited on the command line by the :code:`-D` flag.
+      
 
    This is a documented option
    
diff --git a/tests/examples/sphinx/source/example_no_undocumented.rst b/tests/examples/sphinx/source/example_no_undocumented.rst
index 9a5a9bb..1b3615f 100644
--- a/tests/examples/sphinx/source/example_no_undocumented.rst
+++ b/tests/examples/sphinx/source/example_no_undocumented.rst
@@ -146,10 +146,10 @@ examples.example
    .. note:: 
 
       
-                  This variable is a user-editable option,
-                  meaning it appears within the cache and can be
-                  edited on the command line by the :code:`-D` flag.
-                  
+      This variable is a user-editable option,
+      meaning it appears within the cache and can be
+      edited on the command line by the :code:`-D` flag.
+      
 
    This is a documented option
    
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 8c55746..9bdbb86 100644
--- a/tests/examples/sphinx/source/example_no_undocumented_diff_header.rst
+++ b/tests/examples/sphinx/source/example_no_undocumented_diff_header.rst
@@ -146,10 +146,10 @@ examples.example.cmake
    .. note:: 
 
       
-                  This variable is a user-editable option,
-                  meaning it appears within the cache and can be
-                  edited on the command line by the :code:`-D` flag.
-                  
+      This variable is a user-editable option,
+      meaning it appears within the cache and can be
+      edited on the command line by the :code:`-D` flag.
+      
 
    This is a documented option
    
diff --git a/tests/examples/sphinx/source/example_prefix.rst b/tests/examples/sphinx/source/example_prefix.rst
index e0c8440..0ec5a92 100644
--- a/tests/examples/sphinx/source/example_prefix.rst
+++ b/tests/examples/sphinx/source/example_prefix.rst
@@ -346,10 +346,10 @@ prefix.example
    .. note:: 
 
       
-                  This variable is a user-editable option,
-                  meaning it appears within the cache and can be
-                  edited on the command line by the :code:`-D` flag.
-                  
+      This variable is a user-editable option,
+      meaning it appears within the cache and can be
+      edited on the command line by the :code:`-D` flag.
+      
 
    This is a documented option
    
diff --git a/tests/test_samples/corr_rst/option.rst b/tests/test_samples/corr_rst/option.rst
index e984b10..bab7be4 100644
--- a/tests/test_samples/corr_rst/option.rst
+++ b/tests/test_samples/corr_rst/option.rst
@@ -12,10 +12,10 @@ test_samples.option
    .. note:: 
 
       
-                  This variable is a user-editable option,
-                  meaning it appears within the cache and can be
-                  edited on the command line by the :code:`-D` flag.
-                  
+      This variable is a user-editable option,
+      meaning it appears within the cache and can be
+      edited on the command line by the :code:`-D` flag.
+      
 
    This is a documentation of an option.
    
diff --git a/tests/test_samples/corr_rst/undocumented_option_no_default.rst b/tests/test_samples/corr_rst/undocumented_option_no_default.rst
index 23f2744..7cf90e6 100644
--- a/tests/test_samples/corr_rst/undocumented_option_no_default.rst
+++ b/tests/test_samples/corr_rst/undocumented_option_no_default.rst
@@ -12,10 +12,10 @@ test_samples.undocumented_option_no_default
    .. note:: 
 
       
-                  This variable is a user-editable option,
-                  meaning it appears within the cache and can be
-                  edited on the command line by the :code:`-D` flag.
-                  
+      This variable is a user-editable option,
+      meaning it appears within the cache and can be
+      edited on the command line by the :code:`-D` flag.
+      
 
    
 
diff --git a/tests/unit_tests/test_aggregator.py b/tests/unit_tests/test_aggregator.py
index c0caa2e..9a1a9ef 100755
--- a/tests/unit_tests/test_aggregator.py
+++ b/tests/unit_tests/test_aggregator.py
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+import textwrap
 import unittest
 
 import pytest
@@ -20,13 +21,15 @@ import pytest
 import context
 from antlr4 import *
 
+from cminx import Settings
+from cminx.config import InputSettings
 from cminx.exceptions import CMakeSyntaxException
 from cminx.parser import ParserErrorListener
 from cminx.parser.CMakeLexer import CMakeLexer
 from cminx.parser.CMakeParser import CMakeParser
 from cminx.aggregator import DocumentationAggregator
 from cminx.documentation_types import FunctionDocumentation, MacroDocumentation, VariableDocumentation, \
-    GenericCommandDocumentation, ClassDocumentation, VarType, DocumentationType
+    GenericCommandDocumentation, ClassDocumentation, VarType, DocumentationType, MethodDocumentation
 
 
 class TestAggregator(unittest.TestCase):
@@ -37,7 +40,7 @@ class TestAggregator(unittest.TestCase):
         self.input_stream = FileStream(self.filename)
         self.reset()
 
-    def reset(self):
+    def reset(self, settings=Settings()):
         # Convert those strings into tokens and build a stream from those
         self.lexer = CMakeLexer(self.input_stream)
         self.stream = CommonTokenStream(self.lexer)
@@ -48,7 +51,7 @@ class TestAggregator(unittest.TestCase):
         self.tree = self.parser.cmake_file()
 
         # Hard part is done, we now have a fully useable parse tree, now we just need to walk it
-        self.aggregator = DocumentationAggregator()
+        self.aggregator = DocumentationAggregator(settings)
         self.walker = ParseTreeWalker()
         self.walker.walk(self.aggregator, self.tree)
 
@@ -109,13 +112,13 @@ endmacro()
         var_name = "TEST_VAR"
         val = "This is a value"
 
-        self.input_stream = InputStream(f'''
-#[[[
- {docstring}
-#]]
-set({var_name} "{val}")
+        self.input_stream = InputStream(textwrap.dedent(f'''
+                                #[[[
+                                 {docstring}
+                                #]]
+                                set({var_name} "{val}")
 
-        ''')
+                            '''))
         self.reset()
         self.assertEqual(len(self.aggregator.documented), 1, "Different number of documented commands than expected")
         self.assertEqual(type(self.aggregator.documented[0]), VariableDocumentation, "Unexpected documentation type")
@@ -129,12 +132,12 @@ set({var_name} "{val}")
         var_name = "listvar"
         params = ["param1", "param2"]
 
-        self.input_stream = InputStream(f'''
-#[[[
- {docstring}
-#]]
-set({var_name} {params[0]} {params[1]})
-        ''')
+        self.input_stream = InputStream(textwrap.dedent(f'''
+                                #[[[
+                                {docstring}
+                                #]]
+                                set({var_name} {params[0]} {params[1]})
+                            '''))
         self.reset()
         self.assertEqual(len(self.aggregator.documented), 1, "Different number of documented commands than expected")
         self.assertEqual(type(self.aggregator.documented[0]), VariableDocumentation, "Unexpected documentation type")
@@ -149,12 +152,12 @@ set({var_name} {params[0]} {params[1]})
         docstring = "Unsetting a variable"
         var_name = "myvar"
 
-        self.input_stream = InputStream(f'''
-#[[[
- {docstring}
-#]]
-set({var_name})
-        ''')
+        self.input_stream = InputStream(textwrap.dedent(f'''
+                                #[[[
+                                {docstring}
+                                #]]
+                                set({var_name})
+                            '''))
         self.reset()
         self.assertEqual(len(self.aggregator.documented), 1, "Different number of documented commands than expected")
         self.assertEqual(type(self.aggregator.documented[0]), VariableDocumentation, "Unexpected documentation type")
@@ -176,27 +179,43 @@ set({var_name})
         attribute_docstring = "#[[[\n# This is an attribute\n#]]"
         class_name = "MyClass"
         inner_class_definitions = '\n'.join(
-            [f'{inner_class_docstring}\ncpp_class({inner_class_name})\ncpp_end_class()' for inner_class_name in
-             inner_classes])
-        method_definitions = '\n'.join([
-            f'{method_docstring}\ncpp_member({method_name} {class_name})\nfunction(' + '${' + method_name + '})\nendfunction()'
-            for method_name in methods])
+            [
+                textwrap.dedent(f"""\
+                    {inner_class_docstring}
+                    cpp_class({inner_class_name})
+                    cpp_end_class()
+                """)
+                for inner_class_name in inner_classes
+            ]
+        )
+        # Curly brackets in f strings are escaped with two brackets. So we end up with "${<value of method_name>}"
+        method_definitions = '\n'.join(
+            [
+                textwrap.dedent(f"""\
+                    {method_docstring}
+                    cpp_member({method_name} {class_name})
+                    function(${{{method_name}}})
+                    endfunction()
+                """)
+                for method_name in methods
+            ]
+        )
         attribute_definitions = '\n'.join(
             [f'{attribute_docstring}\ncpp_attr({class_name} {attr_name})' for attr_name in attributes])
-        self.input_stream = InputStream(f'''
-#[[[
-# {class_docstring}
-#]]
-cpp_class({class_name} {' '.join(superclasses)})
+        self.input_stream = InputStream(textwrap.dedent(f'''
+                                #[[[
+                                # {class_docstring}
+                                #]]
+                                cpp_class({class_name} {' '.join(superclasses)})
 
-    {attribute_definitions}
+                                    {attribute_definitions}
 
-    {method_definitions}
+                                    {method_definitions}
 
-    {inner_class_definitions}
+                                    {inner_class_definitions}
 
-cpp_end_class()
-        ''')
+                                cpp_end_class()
+                            '''))
         self.reset()
         self.assertEqual(len(self.aggregator.documented), 1 + len(inner_classes),
                          "Different number of documented commands than expected")
@@ -214,7 +233,7 @@ cpp_end_class()
         self.assertEqual(len(self.aggregator.documented[0].members), len(methods), "Members incorrectly found")
         self.assertEqual(len(self.aggregator.documented[0].attributes), len(attributes), "Attributes incorrectly found")
 
-    def test_cpp_class_multi_superclass_no_inner(self, superclasses=["SuperClassA", "SuperClassB", "SuperClassC"]):
+    def test_cpp_class_multi_superclass_no_inner(self, superclasses=("SuperClassA", "SuperClassB", "SuperClassC")):
         self.test_cpp_class_multi_superclass_multi_members(superclasses=superclasses, attributes=[], methods=[])
 
     def test_cpp_class_one_superclasses_no_inner(self):
@@ -391,6 +410,92 @@ cpp_end_class()
         with pytest.raises(CMakeSyntaxException):
             self.reset()
 
+    def test_function_param_regex(self):
+        func_name = "test_func"
+        stripped_param_names = ["param1", "param2_with_underscores"]
+        param_names = [f"_tf_{name}" for name in stripped_param_names]
+
+        docstring = "This is a function that has its param name regex-stripped"
+
+        regex = "^_[a-zA-Z]*_"
+
+        self.input_stream = InputStream(textwrap.dedent(f"""
+                    #[[[
+                    # {docstring}
+                    #]]
+                    function({func_name} {' '.join(param_names)})
+                    endfunction()
+                """))
+
+        input_settings = InputSettings(function_parameter_name_strip_regex=regex)
+        self.reset(settings=Settings(input=input_settings))
+        self.assertIsInstance(self.aggregator.documented[0], FunctionDocumentation)
+        self.assertEqual(func_name, self.aggregator.documented[0].name)
+        for i in range(0, len(stripped_param_names)):
+            self.assertEqual(
+                stripped_param_names[i], self.aggregator.documented[0].params[i],
+                "Parameter name was not stripped correctly"
+            )
+
+    def test_macro_param_regex(self):
+        func_name = "test_macro"
+        stripped_param_names = ["param1", "param2_with_underscores"]
+        param_names = [f"_tm_{name}" for name in stripped_param_names]
+
+        docstring = "This is a macro that has its param name regex-stripped"
+
+        regex = "^_[a-zA-Z]*_"
+
+        self.input_stream = InputStream(textwrap.dedent(f"""
+            #[[[
+            # {docstring}
+            #]]
+            macro({func_name} {' '.join(param_names)})
+            endmacro()
+        """))
+
+        input_settings = InputSettings(macro_parameter_name_strip_regex=regex)
+        self.reset(settings=Settings(input=input_settings))
+        self.assertIsInstance(self.aggregator.documented[0], MacroDocumentation)
+        self.assertEqual(func_name, self.aggregator.documented[0].name)
+        for i in range(0, len(stripped_param_names)):
+            self.assertEqual(
+                stripped_param_names[i], self.aggregator.documented[0].params[i],
+                "Parameter name was not stripped correctly"
+            )
+
+    def test_method_param_regex(self):
+        func_name = "test_method"
+        stripped_param_names = ["param1", "param2_with_underscores"]
+        param_names = [f"_tm_{name}" for name in stripped_param_names]
+        param_types = ["desc", "str"]
+
+        docstring = "This is a method that has its param name regex-stripped"
+
+        regex = "^_[a-zA-Z]*_"
+
+        self.input_stream = InputStream(textwrap.dedent(f"""
+                    cpp_class(TestClass)
+                        #[[[
+                        # {docstring}
+                        #]]
+                        cpp_member({func_name} TestClass {' '.join(param_types)})
+                        function("${{{func_name}}}" self {' '.join(param_names)})
+                        endfunction()
+                    cpp_end_class()
+                """))
+
+        input_settings = InputSettings(member_parameter_name_strip_regex=regex)
+        self.reset(settings=Settings(input=input_settings))
+        self.assertIsInstance(self.aggregator.documented[0], ClassDocumentation)
+        self.assertIsInstance(self.aggregator.documented[0].members[0], MethodDocumentation)
+        self.assertEqual(func_name, self.aggregator.documented[0].members[0].name)
+        for i in range(0, len(stripped_param_names)):
+            self.assertEqual(
+                stripped_param_names[i], self.aggregator.documented[0].members[0].params[i],
+                "Parameter name was not stripped correctly"
+            )
+
 
 if __name__ == '__main__':
     unittest.main()