Repository: CMakePP/CMinx

Issue:
Toctree directive does not have necessary blank line between options and elements
## Describe the bug
When running CMinx on a directory containing documented CMake functions, the `toctree` directives within the generated `index.rst` files generated are invalid, resulting in many `WARNING: Error in "toctree" directive: invalid option block.` warnings that make the API documentation generated by CMinx unable to be traversed on the documentation website. This appears to be because there is not a blank line between the directive options and elements. For example,
```rst
.. toctree::
   :maxdepth: 2
   file1
   file2
```
(what CMinx generates currently) fails, while
```rst
.. toctree::
   :maxdepth: 2

   file1
   file2
```
succeeds.

## To Reproduce
Steps to reproduce the behavior:
1. Create a directory with a CMake file in it, for example `reproduce/test.cmake`.
2. Add a documented function to the CMake file, for example
```cmake
#[[[
# Test function for CMinx.
#]]
function(test_function)
    message("-- in function")
endfunction()
```
4. Run CMinx on the directory.
```bash
cminx "reproduce/test.cmake" -o "build/api_docs" -r
```
6. Set up a Sphinx documentation directory.
7. Copy the documentation files generated by CMinx into the Sphinx documentation source, ensuring it is included in an `index.rst` file.
8. Try to build the documentation and see `WARNING: Error in "toctree" directive: invalid option block.` errors for the `toctree` directives in the generated `index.rst` files.
9. If you try to host the resulting HTML documentation, the API links on the documentation website will not exist.
10. Add a blank line between the options and elements of the `toctree` directive in the generated `index.rst` file, and the build will work.

## Expected behavior
The `toctree` directives in CMinx-generated `index.rst` files should have a blank line between the options and elements.

## Additional context
I poked around in CMinx and around [L541](https://github.com/CMakePP/CMinx/blob/master/cminx/rstwriter.py#L541) in `rstwriter.py` seems to be the issue. I added `document_string += f"\n"` to L541 and the `index.rst` files were generated properly. I don't know if this breaks anything else though, so further testing is probably needed.


Comments:

Patch:
diff --git a/.gitignore b/.gitignore
index 79c016b..43ea2ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,7 @@ examples/sphinx/build
 # Python virtual environment
 venv/
 virt_env/
+Pipfile*
 
 # Python3 __pycache__
 __pycache__/
diff --git a/cminx/parser/aggregator.py b/cminx/parser/aggregator.py
index e9334a1..4a14a70 100755
--- a/cminx/parser/aggregator.py
+++ b/cminx/parser/aggregator.py
@@ -198,6 +198,8 @@ class DocumentationAggregator(CMakeListener):
               cleaned_lines.append(cleaned_line)
          cleaned_lines[-1] = cleaned_lines[-1].rstrip("#]")
          cleaned_doc = "\n".join(cleaned_lines)
+         if cleaned_doc.startswith("\n"):
+              cleaned_doc = cleaned_doc[1:]
          command = ctx.command_invocation().Identifier().getText().lower()
          if f"process_{command}" in dir(self):
               getattr(self, f"process_{command}")(ctx, cleaned_doc)
diff --git a/cminx/rstwriter.py b/cminx/rstwriter.py
index e15298e..d023bbc 100755
--- a/cminx/rstwriter.py
+++ b/cminx/rstwriter.py
@@ -214,7 +214,7 @@ class Paragraph(object):
 	def build_text_string(self):
 		"""
 		Populates Paragraph.text_string with the
-		RST string cooresponding to this paragraph
+		RST string corresponding to this paragraph
 		"""
 		self.text_string = "\n".join([self.prefix + text for text in self.text.split("\n")])
 
@@ -237,7 +237,7 @@ class Field(object):
 	def build_field_string(self):
 		"""
 		Populates Field.field_string with the
-		RST string cooresponding to this field
+		RST string corresponding to this field
 		"""
 
 		self.field_string = f"\n{self.prefix}:{self.field_name}: {self.field_text}"
@@ -539,6 +539,9 @@ class Directive(RSTWriter):
 		for option in self.options:
 			document_string += f"{option}\n"
 
+		if len(self.document) > 1:
+			document_string += "\n"
+
 		for element in self.document[1:]:
 			document_string += f"{element}\n"
 		return document_string


Test patch:
diff --git a/tests/examples/sphinx/source/example.rst b/tests/examples/sphinx/source/example.rst
index acfa2e1..7a0ee3a 100644
--- a/tests/examples/sphinx/source/example.rst
+++ b/tests/examples/sphinx/source/example.rst
@@ -7,7 +7,7 @@ example.cmake
 
 
 .. function:: say_hi_to(person me)
-   
+
    This function has very basic documentation.
    
    This function's description stays close to idealized formatting and does not do
@@ -22,9 +22,9 @@ example.cmake
 
 .. function:: macro_say_hi(person)
 
+
    .. warning:: This is a macro, and so does not introduce a new scope.
 
-   
    This macro says hi.
    This documentation uses a differing format,
    but is still processed correctly.
@@ -35,7 +35,7 @@ example.cmake
 
 
 .. data:: MyList
-   
+
    This is an example of variable documentation.
    This variable is a list of string values.
    
@@ -46,7 +46,7 @@ example.cmake
 
 
 .. data:: MyString
-   
+
    This is another example of variable documentation.
    This variable is a string variable.
    
@@ -58,9 +58,9 @@ example.cmake
 
 .. function:: message("hello")
 
+
    .. warning:: This is a generic command invocation. It is not a function or macro definition.
 
-   
    This is a generic command invocation.
    It will be documented as well, but with an admonition
    stating it is an invocation and not a definition.
diff --git a/tests/examples/sphinx/source/example_prefix.rst b/tests/examples/sphinx/source/example_prefix.rst
index 784809d..3cd49f1 100644
--- a/tests/examples/sphinx/source/example_prefix.rst
+++ b/tests/examples/sphinx/source/example_prefix.rst
@@ -7,7 +7,7 @@ prefix.example.cmake
 
 
 .. function:: say_hi_to(person me)
-   
+
    This function has very basic documentation.
    
    This function's description stays close to idealized formatting and does not do
@@ -22,9 +22,9 @@ prefix.example.cmake
 
 .. function:: macro_say_hi(person)
 
+
    .. warning:: This is a macro, and so does not introduce a new scope.
 
-   
    This macro says hi.
    This documentation uses a differing format,
    but is still processed correctly.
@@ -35,7 +35,7 @@ prefix.example.cmake
 
 
 .. data:: MyList
-   
+
    This is an example of variable documentation.
    This variable is a list of string values.
    
@@ -46,7 +46,7 @@ prefix.example.cmake
 
 
 .. data:: MyString
-   
+
    This is another example of variable documentation.
    This variable is a string variable.
    
@@ -58,9 +58,9 @@ prefix.example.cmake
 
 .. function:: message("hello")
 
+
    .. warning:: This is a generic command invocation. It is not a function or macro definition.
 
-   
    This is a generic command invocation.
    It will be documented as well, but with an admonition
    stating it is an invocation and not a definition.
diff --git a/tests/test_samples/corr_rst/advanced_function.rst b/tests/test_samples/corr_rst/advanced_function.rst
index f801a4a..588460d 100644
--- a/tests/test_samples/corr_rst/advanced_function.rst
+++ b/tests/test_samples/corr_rst/advanced_function.rst
@@ -7,7 +7,7 @@ advanced_function.cmake
 
 
 .. function:: advanced_say_hi_to(me)
-   
+
    Variation of say_hi_to, which takes lists of people and cats to say hi to.
    
    This function is basically the same as ``say_hi_to``, but accounts for the
diff --git a/tests/test_samples/corr_rst/basic_function.rst b/tests/test_samples/corr_rst/basic_function.rst
index a7bfa45..4d34f54 100644
--- a/tests/test_samples/corr_rst/basic_function.rst
+++ b/tests/test_samples/corr_rst/basic_function.rst
@@ -7,7 +7,7 @@ basic_function.cmake
 
 
 .. function:: say_hi_to(person)
-   
+
    This function has very basic documentation.
    
    This function's description stays close to idealized formatting and does not do
diff --git a/tests/test_samples/corr_rst/basic_macro.rst b/tests/test_samples/corr_rst/basic_macro.rst
index 9892a39..6d41c6f 100644
--- a/tests/test_samples/corr_rst/basic_macro.rst
+++ b/tests/test_samples/corr_rst/basic_macro.rst
@@ -8,9 +8,9 @@ basic_macro.cmake
 
 .. function:: macro_say_hi(person)
 
+
    .. warning:: This is a macro, and so does not introduce a new scope.
 
-   
    This macro says hi.
    This documentation uses a differing format,
    but is still processed correctly.
diff --git a/tests/test_samples/corr_rst/ct_test.rst b/tests/test_samples/corr_rst/ct_test.rst
index 656dff1..e1faac6 100644
--- a/tests/test_samples/corr_rst/ct_test.rst
+++ b/tests/test_samples/corr_rst/ct_test.rst
@@ -8,18 +8,18 @@ ct_test.cmake
 
 .. function:: test1(EXPECTFAIL)
 
+
    .. warning:: This is a CMakeTest test definition, do not call this manually.
 
-   
    This is how you document a CMakeTest test.
    
 
 
 .. function:: section1(EXPECTFAIL)
 
+
    .. warning:: This is a CMakeTest section definition, do not call this manually.
 
-   
    And this is how you document a CMakeTest section.
    
 
diff --git a/tests/test_samples/corr_rst/index.rst b/tests/test_samples/corr_rst/index.rst
index 26415d1..b6250b0 100644
--- a/tests/test_samples/corr_rst/index.rst
+++ b/tests/test_samples/corr_rst/index.rst
@@ -5,6 +5,7 @@
 
 .. toctree::
    :maxdepth: 2
+
    basic_macro
    basic_function
    quickstart
diff --git a/tests/test_samples/corr_rst/quickstart.rst b/tests/test_samples/corr_rst/quickstart.rst
index 50881a2..403a9ff 100644
--- a/tests/test_samples/corr_rst/quickstart.rst
+++ b/tests/test_samples/corr_rst/quickstart.rst
@@ -7,7 +7,7 @@ quickstart.cmake
 
 
 .. function:: quickstart(param0)
-   
+
    A brief description.
    
    A more detailed description, must be separated from the brief by at least
diff --git a/tests/test_samples/corr_rst/variable.rst b/tests/test_samples/corr_rst/variable.rst
index 5d292d8..c14d09b 100644
--- a/tests/test_samples/corr_rst/variable.rst
+++ b/tests/test_samples/corr_rst/variable.rst
@@ -7,7 +7,7 @@ variable.cmake
 
 
 .. data:: MyList
-   
+
    This is an example of variable documentation.
    This variable is a list of string values.
    
diff --git a/tests/test_samples/test_samples.py b/tests/test_samples/test_samples.py
index 66ee5b3..baa83d6 100644
--- a/tests/test_samples/test_samples.py
+++ b/tests/test_samples/test_samples.py
@@ -49,4 +49,4 @@ class TestSamples(unittest.TestCase):
             generated_path = os.path.join(self.output_dir, file_name)
             corr_path = os.path.join(self.corr_dir, file_name)
             diff = helpers.diff_files(generated_path, corr_path)
-            self.assertTrue(diff == "")
+            self.assertEqual(diff, "")
diff --git a/tests/unit_tests/rst_validator.py b/tests/unit_tests/rst_validator.py
index aeb1288..c12ce63 100755
--- a/tests/unit_tests/rst_validator.py
+++ b/tests/unit_tests/rst_validator.py
@@ -216,7 +216,7 @@ class RSTValidator:
 
         if ad[0].astext() != to_validate.document[1].text:
             self.fail(
-                "Parsed admonition text does not match original admonition text"
+                f"Parsed admonition text ({ad[0].astext()}) does not match original admonition text ({to_validate.document[1].text}"
             )
 
     def process_image(self, img: image, to_validate: Directive):
diff --git a/tests/unit_tests/test_init.py b/tests/unit_tests/test_init.py
index 0cd26b3..4673c44 100755
--- a/tests/unit_tests/test_init.py
+++ b/tests/unit_tests/test_init.py
@@ -23,7 +23,8 @@ import cminx
 import helpers
 
 class TestInit(unittest.TestCase):
-    """ Tests the functions found in __init__.py
+    """
+    Tests the functions found in __init__.py
     """
 
     def setUp(self):
@@ -84,7 +85,7 @@ class TestInit(unittest.TestCase):
       self.assertTrue(is_file, "Output file does not exist")
 
       diff = helpers.diff_files(self.output_file, context.corr_example_rst)
-      self.assertTrue(diff == "")
+      self.assertEqual(diff, "")
 
     def test_recursive_prefix(self):
       """Tests the use of CMinx in recursive mode with a prefix"""
@@ -100,8 +101,7 @@ class TestInit(unittest.TestCase):
       self.assertTrue(is_file, "Output file does not exist")
 
       diff = helpers.diff_files(self.output_file, context.corr_example_prefix_rst)
-
-      self.assertTrue(diff == "")
+      self.assertEqual(diff, "")
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/unit_tests/test_rstwriter.py b/tests/unit_tests/test_rstwriter.py
index b0c9cff..0acdc36 100755
--- a/tests/unit_tests/test_rstwriter.py
+++ b/tests/unit_tests/test_rstwriter.py
@@ -102,6 +102,21 @@ class TestRSTWriter(unittest.TestCase):
 		self.assertTrue(text in str(self.writer), f"Subsection text was not included")
 		self.assertTrue(self.validator.validate(), self.validator.failures)
 
+	def test_directive(self):
+		dir_type = "attention"
+
+		opts = {"name" : "attention"}
+		dir_text = "This is an attention admonition"
+
+		directive = self.writer.directive(dir_type)
+		for opt_type, opt_val in opts.items():
+			directive.option(opt_type, opt_val)
+
+		directive.text(dir_text)
+
+		self.assertTrue(dir_type in str(self.writer), f"Directive {dir_type} was not included")
+		self.assertTrue(self.validator.validate(), self.validator.failures)
+
 	def test_write(self):
 		self.writer.write_to_file(self.file)
 		with open(self.file, "w") as f: