Repository: cogent3/cogent3 (python)

Issue:
Add a code example to the `min_length` app
**What type of documentation**

Related to #1636.

Add a code snippet to the doctring for `cogent3.app.sample.min_length`. To make it explicit to a user we should construct the sample data in the example, e.g.

```python
>>> from cogent3 import make_aligned_seqs, get_app
>>>
>>> aln = make_aligned_seqs({"s1": "ACGGT", "s2": "AC-GT"}, moltype="dna")
>>> app = get_app("min_length", 3)  # minimum length of 3
>>> result = app(aln)
>>> result
... 2x 5 dna alignment
```

And be clear what happens if they don't specify a moltype, or if an alignment (or sequence collection) does not satisfy the condition.

Also check what that looks like when you run `app_help("min_length")` in Jupyter


Pull request description:
DOC:  Add `min_length` app docstring, fixes #1681
Addresses #1681. I think something went wrong with the rebase...

Patch:
diff --git a/.gitignore b/.gitignore
index 934914535..f557c8489 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,5 +49,8 @@ __pycache__
 .vscode/*
 venv/
 working/
+*.ipynb
+*.ipynb_checkpoints/
+
 # vi
 .*.swp
diff --git a/.hgignore b/.hgignore
index 4b3780448..b5a6403a3 100644
--- a/.hgignore
+++ b/.hgignore
@@ -20,6 +20,7 @@ draw_results
 *.wpu
 .cache*
 *taskpaper
+*.ipynb
 *.ipynb_checkpoints*
 *.sublime*
 *.patch
@@ -35,4 +36,4 @@ doc/draw*
 dist/*
 working/*
 lcov*.info
-.ruff_cache/*
\ No newline at end of file
+.ruff_cache/*
diff --git a/src/cogent3/app/__init__.py b/src/cogent3/app/__init__.py
index c126c3eca..c02e9a330 100644
--- a/src/cogent3/app/__init__.py
+++ b/src/cogent3/app/__init__.py
@@ -197,8 +197,13 @@ def _clean_params_docs(text: str) -> str:
     doc = []
     for line in text:
         line = prefix.sub("", line)
-        if line.strip():
-            doc.append(line)
+        doc.append(line)
+
+    # Remove empty lines at the beginning and end of docstrings
+    if not doc[0]:
+        doc.pop(0)
+    if not doc[-1]:
+        doc.pop()
 
     return "\n".join(doc)
 
@@ -214,8 +219,8 @@ def app_help(name: str):
     Parameters
     ----------
     name
-        app name, e.g. 'minlength', or can include module information,
-        e.g. 'cogent3.app.sample.minlength' or 'sample.minlength'. Use the
+        app name, e.g. 'min_length', or can include module information,
+        e.g. 'cogent3.app.sample.min_length' or 'sample.min_length'. Use the
         latter (qualified class name) style when there are multiple matches
         to name.
     """
diff --git a/src/cogent3/app/sample.py b/src/cogent3/app/sample.py
index a335f944e..22a730b34 100644
--- a/src/cogent3/app/sample.py
+++ b/src/cogent3/app/sample.py
@@ -366,6 +366,36 @@ class min_length:
             degenerate characters subtracted from sequence length calculation
         moltype
             molecular type, can be string or instance
+
+        Examples
+        --------
+        Create a sample alignment. Alignments must have a moltype specified
+
+        >>> from cogent3 import make_aligned_seqs, get_app
+        >>> aln = make_aligned_seqs({"s1": "ACGGT", "s2": "AC-GT"}, moltype="dna")
+
+        Create the app to filter sequences with a minimum length of 3 sites
+
+        >>> app = get_app("min_length", 3)
+        >>> result = app(aln)
+        >>> len(result)
+        5
+
+        When all sequences are shorter than the min length, returns a
+        NotCompleted (see https://cogent3.org/doc/app/not-completed.html)
+
+        >>> app = get_app("min_length", 7)
+        >>> result = app(aln)
+        >>> result.message
+        '4 < min_length 7'
+
+        If the moltype of the alignment is not specified, returns a NotCompleted
+
+        >>> aln = make_aligned_seqs({"s1": "ACGGT", "s2": "AC-GT"})
+        >>> app = get_app("min_length", 3)
+        >>> result = app(aln)
+        >>> result.message
+        'Traceback...
         """
         if motif_length > 1:
             length = length // motif_length


Test patch:
diff --git a/tests/test_app/test_init.py b/tests/test_app/test_init.py
index 0f4db0228..06fe2e17b 100644
--- a/tests/test_app/test_init.py
+++ b/tests/test_app/test_init.py
@@ -196,6 +196,25 @@ def test_app_help_no_docs(capsys, app_doc, init_doc):
         assert "Options" in got
 
 
+def test_app_help_doctest_examples(capsys):
+    app_doc = "A line of text describing the app."
+    init_doc = "\n        Parameters\n        ----------\n        arg\n        arg description\n\n        Examples\n        --------\n        How to use the app\n\n        >>>blah(arg)\n        output\n"
+    blah.__doc__ = app_doc
+    blah.__init__.__doc__ = init_doc
+    app_help("blah")
+    got = capsys.readouterr().out
+    # Two new lines after the end of Parameters should be preserved
+    expect1 = "arg description\n\nExamples\n--------\n"
+    # Two new lines within Examples are preserved
+    expect2 = "How to use the app\n\n>>>blah(arg)\noutput\n"
+    # Two new lines at the end of Examples preserved
+    expect3 = "output\n\nInput type"
+
+    assert expect1 in got
+    assert expect2 in got
+    assert expect3 in got
+
+
 @pytest.mark.parametrize(
     "app_name", ("bootstrap", "from_primitive", "load_db", "take_named_seqs")[:1]
 )