Metadata-Version: 2.4
Name: asciidoctest
Version: 0.2.0a1
Summary: Verifiable, stateful, and interactive documentation with AsciiDoc.
Author-email: Michael Bernstein <zopemaven@gmail.com>
License: Apache-2.0
Project-URL: Homepage, https://github.com/webmaven/asciidoctest
Project-URL: Repository, https://github.com/webmaven/asciidoctest
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Framework :: Pytest
Requires-Python: >=3.14
Description-Content-Type: text/plain
License-File: LICENSE
Requires-Dist: asciidoctrine>=0.1.0a8
Requires-Dist: asciidocstring>=0.1.0a4
Requires-Dist: lark>=1.3.1
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: pytest-cov>=4.0; extra == "test"
Dynamic: license-file

= AsciiDoctest: Verifiable, Stateful, and Interactive Documentation with AsciiDoc
:toc: left
:idprefix:
:idseparator: -

image:https://github.com/webmaven/asciidoctest/actions/workflows/ci.yml/badge.svg[CI Status, link=https://github.com/webmaven/asciidoctest/actions/workflows/ci.yml]
image:https://img.shields.io/pypi/v/asciidoctest.svg[PyPI Version, link=https://pypi.org/project/asciidoctest/]
image:https://img.shields.io/badge/python-3.14-blue.svg[Python Version]
image:https://img.shields.io/badge/coverage-97%25-green.svg[Test Coverage]
image:https://img.shields.io/badge/License-Apache_2.0-blue.svg[License, link=https://opensource.org/licenses/Apache-2.0]

AsciiDoctest is an executable documentation runner and narrative testing tool designed to parse, collect, and verify Python code blocks directly from AsciiDoc (`.adoc`) files and Python docstrings. 

It integrates AST-based parsing using `asciidoctrine` and `asciidocstring` to provide accurate, standard-compliant testing of your documentation's code examples.

== Why AsciiDoctest?

Traditional doctest extractors rely on fragile regular expressions to parse block structures and find code examples. This approach frequently breaks when encountering inline styles, unparsed attributes, nested lists, or complex block boundaries.

AsciiDoctest solves this by relying on native **Abstract Syntax Tree (AST)** parsing:
* Leverages full structural documents parsed via `asciidoctrine` and `asciidocstring`.
* Fully honors standard AsciiDoc roles, positional arguments, attributes, and includes.
* Maintains high-fidelity source coordinates (line and column mappings) for precise failure reporting.

== Executable Documentation State Models

Writing a multi-step tutorial, interactive guide, or documentation book often requires code examples that build on top of each other. At the same time, writers need the ability to run isolated one-off checks or explore alternative paths without leaking state.

AsciiDoctest provides a unified, symmetric state model across both interactive REPL blocks and non-interactive script blocks to facilitate predictable, stateful documentation.

A Python code block is considered **marked** when it contains the `test` or `shared` keyword inside its block header—either as a positional argument (e.g., `[source,python,test]`), an explicit attribute (e.g., `[source,python,test="true"]`), or a block role (e.g., `[source,python,role="shared"]`).

Based on these markers, blocks are executed under the following models:

* **No Marker or Attribute**: Treated as an ordinary, non-executable code listing.

  - *Exception*: In `eager` mode, unmarked listings are executed as `test` blocks (fully isolated and ephemeral) ONLY if no block in the entire document has any explicit markers of either sort.
* **`test` (Isolated & Ephemeral)**: Runs in a completely clean, isolated namespace (`{}`). Any variables or state changes created during its execution are immediately discarded. This aligns with standard unit-testing isolation principles.
* **`shared` (Read-Write & Persistent)**: Participates in a continuous, stateful document timeline (like a stateful notebook). Any classes, variables, or functions defined in early `shared` blocks are fully accessible and modifiable in subsequent `shared` blocks.
* **`shared, test` (Ephemeral Copy)**: Gets access to a read-only copy of the shared state *at that point in the document*, but any mutations or local bindings created within the block are discarded when the block completes.

This model is extremely consistent, easy to reason about, and ensures that your documentation's code examples are always accurate and tested.


== Features

* **AST-Based Parsing**: Structural parsing using `asciidoctrine` (AsciiDoc parser) and `asciidocstring` (AsciiDoc docstring parser).
* **Execution Modes**:
  - `explicit` (default): Only executes blocks with the `test` or `shared` markers.
  - `eager`: Falls back to executing all `[source,python]` listings as isolated `test` blocks, but only if the document contains zero explicit markers.
* **Pytest Integration**: Automatic discovery and execution of `.adoc` files and Python docstrings via registered pytest collectors.
* **Unittest Compatibility**: Suite wrappers (`DocTestSuite` and `DocFileSuite`) designed to integrate with the standard library `unittest` runner.

== Installation

[source,bash]
----
pip install asciidoctest
----

== Pytest Integration

AsciiDoctest automatically registers as a `pytest` plugin. Simply execute `pytest` in your project directory:

[source,bash]
----
pytest
----

=== Configuration

You can configure collection behavior in your `pyproject.toml` or `pytest.ini`:

[source,ini]
----
[pytest]
asciidoctest_mode = eager
----

Alternatively, you can supply the `--asciidoctest-mode` flag:

[source,bash]
----
pytest --asciidoctest-mode=eager
----

== Unittest Integration

To use the standard library `unittest` package, load tests using `DocFileSuite` or `DocTestSuite`:

[source,python]
----
import unittest
from asciidoctest import DocFileSuite

def suite():
    return DocFileSuite("README.adoc")

if __name__ == "__main__":
    unittest.main(defaultTest="suite")
----

== Examples

Below are standard test blocks demonstrating interactive and script-based execution.

=== Interactive Session

We can run interactive Python sessions with expected outputs. We mark this block with `[source,python,shared]` to allow this interactive block's setup state (`x`) to be persisted for subsequent blocks:

[source,python,shared]
----
>>> x = "asciidoctest"
>>> x.upper()
'ASCIIDOCTEST'
----

=== Sequential Script Block

We can run script-based test blocks with standard Python assertions. Since they share the same continuous narrative namespace, variables defined in previous `[source,python,shared]` blocks are accessible. We mark this block with `[source,python,shared]`:

[source,python,shared]
----
assert x == "asciidoctest"
y = len(x)
assert y == 12
----

=== Directives Support

Standard `doctest` directives such as `ELLIPSIS` are supported. We mark this block with `[source,python,test]` so that it is independent and runs with a clean, isolated namespace:


[source,python,test]
----
>>> print(x)
Traceback (most recent call last):
    ...
NameError: name 'x' is not defined
>>> x = "Hello, beautiful world!"
>>> print(x)
Hello, ... world!
----

=== Ephemeral Copy of Shared State

We can grant a test block access to an ephemeral copy of the accumulated shared state at that point in the document. Any modifications made within the block are discarded afterwards. We mark this block with `[source,python,shared,test]`:

[source,python,shared,test]
----
>>> print(x)
asciidoctest
>>> x = "Hello, beautiful world!"
>>> print(x)
Hello, ... world!
----

To demonstrate that the changes in the `shared, test` block were indeed discarded and did not modify the persistent shared namespace, a subsequent `[source,python,shared]` block shows that `x` remains unchanged:

[source,python,shared]
----
>>> print(x)
asciidoctest
----



== Contributing

We welcome contributions! AsciiDoctest maintains strict quality standards:
* Fully annotated Python types.
* Comprehensive unit and integration test coverage kept above **95%**.

To get started on development locally:

1. Clone and set up the virtual environment:
+
[source,bash]
----
git clone https://github.com/webmaven/asciidoctest.git
cd asciidoctest
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[test]"
----

2. Run the test suite:
+
[source,bash]
----
pytest
----

3. Check code coverage reports:
+
[source,bash]
----
coverage run -m pytest
coverage report -m
----

== License

Licensed under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at:

link:https://www.apache.org/licenses/LICENSE-2.0[https://www.apache.org/licenses/LICENSE-2.0]

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
