Skip to content

Commit dfba8a2

Browse files
committed
Drop Py 3.8 and 3.9; add extra for ANSI extension and fix tests on its new version; add explicit docutils dep.
Fixes #80. Fixes #83. Fixes #82. Fixes #79. Fixes #78.
1 parent 50c74a5 commit dfba8a2

6 files changed

Lines changed: 28 additions & 44 deletions

File tree

.github/workflows/tests.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ jobs:
1313
strategy:
1414
matrix:
1515
python-version:
16-
- "3.8"
17-
- "3.9"
1816
- "3.10"
1917
- "3.11"
2018
- "3.12"
@@ -38,7 +36,7 @@ jobs:
3836
run: |
3937
tox -v -e py
4038
- name: Lint
41-
if: matrix.python-version == '3.12'
39+
if: matrix.python-version == '3.14'
4240
run: |
4341
python -m pip install -U pylint ".[test]"
4442
pylint src

CHANGES.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
0.20 (unreleased)
66
=================
77

8-
- Nothing changed yet.
8+
- Stop testing Python 3.8 and 3.9. Only 3.10 and above are supported.
9+
- Add an ``ansi`` extra to install ``erbsland-sphinx-ansi``. Version
10+
1.2.4 or later is required.
11+
- Explicitly list ``docutils`` as a dependency.
912

1013

1114
0.19 (2026-02-20)

setup.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def read_version_number():
5353
# method is invoked. So we now have to test side effects.
5454
# That's OK, and the same side effect test works on older
5555
# versions as well.
56-
"erbsland-sphinx-ansi; python_version >= '3.10'",
56+
"erbsland-sphinx-ansi >= 1.2.4'",
5757
]
5858

5959
setup(
@@ -84,8 +84,6 @@ def read_version_number():
8484
'Operating System :: OS Independent',
8585
'Programming Language :: Python',
8686
'Programming Language :: Python :: 3 :: Only',
87-
"Programming Language :: Python :: 3.8",
88-
"Programming Language :: Python :: 3.9",
8987
"Programming Language :: Python :: 3.10",
9088
"Programming Language :: Python :: 3.11",
9189
"Programming Language :: Python :: 3.12",
@@ -103,13 +101,17 @@ def read_version_number():
103101
package_dir={'': 'src'},
104102
include_package_data=True,
105103
install_requires=[
106-
'Sphinx>=5.0.0',
104+
'Sphinx >= 5.0.0',
105+
'docutils',
107106
],
108107
extras_require={
109108
'test': tests_require,
110109
'docs': [
111110
'furo',
112111
],
112+
'ansi': [
113+
"erbsland-sphinx-ansi >= 1.2.4"
114+
],
113115
},
114-
python_requires=">=3.8",
116+
python_requires=">=3.10",
115117
)

src/sphinxcontrib/programoutput/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def _create_output_node(output, use_ansi, app=None):
116116
)
117117
stripped_output = _strip_ansi_formatting(output)
118118
return nodes.literal_block(stripped_output, stripped_output)
119-
return ANSILiteralBlock(output, output)
119+
return ANSILiteralBlock(output)
120120

121121

122122
class ProgramOutputDirective(rst.Directive):

src/sphinxcontrib/programoutput/tests/test_directive.py

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,19 @@ class TestDirective(AppMixin,
7171
# pylint:disable=too-many-public-methods
7272
def assert_output(self, doctree, output, **kwargs):
7373
__tracebackhide__ = True
74-
literal = doctree.next_node(literal_block)
75-
self.assertTrue(literal)
76-
self.assertEqual(literal.astext(), output)
74+
# Sometime around 1.2.4, erpsland-sphinx-ansi changed from being a
75+
# ``literal_block`` to being a ``container``, and its astext()
76+
# stopped being the
77+
# See
78+
# https://github.com/erbsland-dev/erbsland-sphinx-ansi
79+
# /commit/f9b2481b65ac4df208bc0dc47b433ecefbbb0701
80+
# #diff-501301f0162ce64234424c0fa1e1b56724e0d526c3d364d9db3ddc19931f2a27
81+
using_ansi = kwargs.get('ansi')
82+
literal = doctree.next_node(literal_block
83+
if not using_ansi
84+
else container)
85+
self.assertTrue(literal, (literal, output))
86+
self.assertEqual(literal.astext() if not using_ansi else literal.rawsource, output)
7787

7888
if 'caption' in kwargs:
7989
caption_node = doctree.next_node(caption)
@@ -106,7 +116,6 @@ def test_simple(self):
106116
self.assert_output(self.doctree, 'eggs')
107117
self.assert_cache(self.app, 'echo eggs', 'eggs')
108118

109-
110119
@with_content("""\
111120
.. program-output:: python -c 'print("spam with eggs")'""")
112121
def test_with_spaces(self):
@@ -120,7 +129,6 @@ def test_with_spaces(self):
120129
self.assert_cache(self.app, sys.executable + ' -c \'print("spam with eggs")\'',
121130
'spam with eggs')
122131

123-
124132
@with_content("""\
125133
.. program-output:: python -c 'import sys; sys.stderr.write("spam with eggs")'
126134
""")
@@ -130,17 +138,6 @@ def test_standard_error(self):
130138
cmd = sys.executable + ' -c \'import sys; sys.stderr.write("spam with eggs")\''
131139
self.assert_cache(self.app, cmd, output)
132140

133-
134-
@with_content("""\
135-
.. program-output:: python -V
136-
:nostderr:""")
137-
@unittest.skipIf(sys.version_info[0] > 2,
138-
reason="Python 3 prints version to stdout, not stderr")
139-
def test_standard_error_disabled(self):
140-
self.assert_output(self.doctree, '')
141-
self.assert_cache(self.app, sys.executable + ' -V', '', hide_standard_error=True)
142-
143-
144141
@with_content("""\
145142
.. program-output:: python -c 'import os; print(os.getcwd())'""")
146143
def test_working_directory_defaults_to_srcdir(self):
@@ -170,7 +167,6 @@ def test_working_directory_relative_to_document(self):
170167
self.assert_cache(self.app, sys.executable + " -c 'import os; print(os.getcwd())'", output,
171168
working_directory=str(contentdir))
172169

173-
174170
@with_content("""\
175171
.. program-output:: echo "${PWD}"
176172
:shell:
@@ -183,21 +179,18 @@ def test_working_directory_with_shell(self):
183179
self.assert_cache(self.app, 'echo "${PWD}"', output, use_shell=True,
184180
working_directory=str(contentdir))
185181

186-
187182
@with_content('.. program-output:: echo "${HOME}"')
188183
def test_no_expansion_without_shell(self):
189184
self.assert_output(self.doctree, '${HOME}')
190185
self.assert_cache(self.app, 'echo "${HOME}"', '${HOME}')
191186

192-
193187
@with_content("""\
194188
.. program-output:: echo "${HOME}"
195189
:shell:""")
196190
def test_expansion_with_shell(self):
197191
self.assert_output(self.doctree, os.environ['HOME'])
198192
self.assert_cache(self.app, 'echo "${HOME}"', os.environ['HOME'], use_shell=True)
199193

200-
201194
@with_content("""\
202195
.. program-output:: echo "spam with eggs"
203196
:prompt:""")
@@ -207,15 +200,13 @@ def test_prompt(self):
207200
spam with eggs""")
208201
self.assert_cache(self.app, 'echo "spam with eggs"', 'spam with eggs')
209202

210-
211203
@with_content('.. command-output:: echo "spam with eggs"')
212204
def test_command(self):
213205
self.assert_output(self.doctree, """\
214206
$ echo "spam with eggs"
215207
spam with eggs""")
216208
self.assert_cache(self.app, 'echo "spam with eggs"', 'spam with eggs')
217209

218-
219210
@with_content('.. command-output:: echo spam',
220211
programoutput_prompt_template='>> {command}\n<< {output}')
221212
def test_command_non_default_prompt(self):
@@ -256,7 +247,6 @@ def test_ellipsis_stop_only(self):
256247
self.assert_cache(self.app, sys.executable + ' -c \'print("spam\\nwith\\neggs")\'',
257248
'spam\nwith\neggs')
258249

259-
260250
@with_content("""\
261251
.. program-output:: python -c 'print("spam\\nwith\\neggs")'
262252
:ellipsis: -2""")
@@ -266,7 +256,6 @@ def test_ellipsis_negative_stop(self):
266256
sys.executable + """ -c 'print("spam\\nwith\\neggs")'""",
267257
'spam\nwith\neggs')
268258

269-
270259
@with_content("""\
271260
.. program-output:: python -c 'print("spam\\nwith\\neggs")'
272261
:ellipsis: 1, 2""")
@@ -276,7 +265,6 @@ def test_ellipsis_start_and_stop(self):
276265
sys.executable + """ -c 'print("spam\\nwith\\neggs")'""",
277266
'spam\nwith\neggs')
278267

279-
280268
@with_content("""\
281269
.. program-output:: python -c 'print("spam\\nwith\\neggs")'
282270
:ellipsis: 1, -1""")
@@ -286,7 +274,6 @@ def test_ellipsis_start_and_negative_stop(self):
286274
sys.executable + """ -c 'print("spam\\nwith\\neggs")'""",
287275
'spam\nwith\neggs')
288276

289-
290277
@with_content("""\
291278
.. program-output:: python -c 'import sys; sys.exit(1)'""",
292279
ignore_warnings=False)
@@ -301,8 +288,6 @@ def test_unexpected_return_code(self):
301288
parsed_command = (sys.executable, '-c', 'import sys; sys.exit(1)')
302289
self.assertIn(repr(parsed_command), repr(command))
303290

304-
305-
306291
@with_content("""\
307292
.. program-output:: python -c 'import sys; sys.exit("some output")'
308293
:shell:""",
@@ -318,7 +303,6 @@ def test_shell_with_unexpected_return_code(self):
318303
# Python 2 include the u'' prefix on the output string.
319304
self.assertEqual('some output', output)
320305

321-
322306
@with_content("""\
323307
.. program-output:: python -c 'import sys; print("foo"); sys.exit(1)'
324308
:returncode: 1""")
@@ -397,7 +381,6 @@ def test_bytes_prompt_with_unicode_output(self):
397381
'echo "U+2264 ≤ LESS-THAN OR EQUAL TO"',
398382
'U+2264 ≤ LESS-THAN OR EQUAL TO')
399383

400-
401384
@with_content("""\
402385
.. program-output:: python -c 'print("U+2264 ≤ LESS-THAN OR EQUAL TO\\n≤ line2\\n≤ line3")'
403386
:ellipsis: 2
@@ -487,13 +470,11 @@ def test_use_ansi_missing_extension(self):
487470
.. program-output:: python -c 'print("\\x1b[31mspam\\x1b[0m")'""",
488471
programoutput_use_ansi=True,
489472
extensions=['sphinxcontrib.programoutput', 'erbsland.sphinx.ansi'])
490-
@unittest.skipIf(sys.version_info[:2] < (3, 10),
491-
"The extension is only available on 3.10+")
492473
def test_use_ansi_enabled_extension(self):
493474
with Patch('sphinxcontrib.programoutput.logger.warning') as patch_warning:
494475
doctree = self.doctree
495476

496-
self.assert_output(doctree, '\x1b[31mspam\x1b[0m')
477+
self.assert_output(doctree, '\x1b[31mspam\x1b[0m', ansi=True)
497478
patch_warning.assert_not_called()
498479
self.assert_cache(
499480
self.app,

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist=py38,py39,py310,py311,py312,py313,py314,pypy3,docs
2+
envlist=py310,py311,py312,py313,py314,pypy3,docs
33

44
[testenv]
55
usedevelop = false

0 commit comments

Comments
 (0)