Coverage for pycommons / dev / tests / examples_in_md.py: 100%
39 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 03:04 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 03:04 +0000
1"""Test all the `Python` example code in a markdown file."""
2from os import getcwd
3from typing import Final
5from pycommons.dev.tests.compile_and_run import compile_and_run
6from pycommons.io.console import logger
7from pycommons.io.path import Path, directory_path, file_path
10def check_examples_in_md(file: str) -> None:
11 """
12 Test all the example Python codes in a markdown file.
14 :param file: the file
15 :raises TypeError: if `file` is not a string
16 :raises ValueError: if `file` is empty or otherwise invalid
18 >>> from contextlib import redirect_stdout
19 >>> with redirect_stdout(None):
20 ... check_examples_in_md(file_path(file_path(__file__).up(
21 ... 4).resolve_inside("README.md")))
23 >>> try:
24 ... check_examples_in_md(1)
25 ... except TypeError as te:
26 ... print(te)
27 descriptor '__len__' requires a 'str' object but received a 'int'
29 >>> try:
30 ... check_examples_in_md(None)
31 ... except TypeError as te:
32 ... print(te)
33 descriptor '__len__' requires a 'str' object but received a 'NoneType'
35 >>> try:
36 ... check_examples_in_md("")
37 ... except ValueError as ve:
38 ... print(ve)
39 Path must not be empty.
41 >>> try:
42 ... check_examples_in_md("/")
43 ... except ValueError as ve:
44 ... print(ve)
45 Path '/' does not identify a file.
46 """
47 # First, we load the README.md file as a single string
48 readme: Final[Path] = file_path(file)
49 logger(f"Executing all examples from file {readme!r}.")
50 text: Final[str] = readme.read_all_str()
51 logger(f"Read {str.__len__(text)} characters from {readme!r}.")
53 i2: int = -1
54 # All examples start and end with ``` after a newline.
55 mark1: Final[str] = "\n```"
56 mark2: Final[str] = "python" # python code starts with ```python
58 wd: Final[str] = directory_path(getcwd()) # current working directory
59 logger(f"Current working directory is {wd!r}.")
60 example_cnt: int = 0
62 while True:
63 # First, find the starting mark.
64 i2 += 1
65 i1 = text.find(mark1, i2)
66 if i1 <= i2:
67 break # no starting mark anymore: done
68 i1 += len(mark1)
69 i2 = text.find(mark1, i1)
70 if i2 <= i1:
71 raise ValueError(f"No end mark for start mark {mark1!r} "
72 f"at character {i1}?")
74 orig_fragment: str = text[i1:i2] # get the fragment
75 fragment: str = str.strip(orig_fragment)
76 if str.__len__(fragment) <= 0:
77 raise ValueError(f"Empty fragment {orig_fragment!r} from "
78 f"{i1} to {i2}?")
79 i2 += str.__len__(mark1)
80 if fragment.startswith(mark2): # it is a python fragment
81 i3 = fragment.find("\n")
82 if i3 < str.__len__(mark2):
83 raise ValueError("Did not find newline in stripped "
84 f"fragment {orig_fragment!r}?")
85 compile_and_run(fragment[i3 + 1:], f"{readme}:{i1}:{i2}")
86 example_cnt += 1
88 if example_cnt <= 0:
89 raise ValueError(f"No example found in {readme!r}.")
90 logger(
91 f"Successfully executed all {example_cnt} examples from {readme!r}.")