Skip to content

Commit

Permalink
feat: add NumberedHeadingsPreprocessor
Browse files Browse the repository at this point in the history
  • Loading branch information
Mathias Millet committed Oct 19, 2024
1 parent e159962 commit 490bb79
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/source/api/preprocessors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Converting text

.. autoclass:: HighlightMagicsPreprocessor

.. autoclass:: NumberedHeadingsPreprocessor

Metadata and header control
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 2 additions & 0 deletions nbconvert/preprocessors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .extractoutput import ExtractOutputPreprocessor
from .highlightmagics import HighlightMagicsPreprocessor
from .latex import LatexPreprocessor
from .numbered_headings import NumberedHeadingsPreprocessor
from .regexremove import RegexRemovePreprocessor
from .svg2pdf import SVG2PDFPreprocessor
from .tagremove import TagRemovePreprocessor
Expand All @@ -30,6 +31,7 @@
"ExtractOutputPreprocessor",
"HighlightMagicsPreprocessor",
"LatexPreprocessor",
"NumberedHeadingsPreprocessor",
"RegexRemovePreprocessor",
"SVG2PDFPreprocessor",
"TagRemovePreprocessor",
Expand Down
51 changes: 51 additions & 0 deletions nbconvert/preprocessors/numbered_headings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Preprocessor that transforms markdown cells: Insert numbering in from of heading
"""

import re

from nbconvert.preprocessors.base import Preprocessor


class NumberedHeadingsPreprocessor(Preprocessor):
"""Pre-processor that will rewrite markdown headings to include numberings."""

def __init__(self, *args, **kwargs):
"""Init"""
super().__init__(*args, **kwargs)
self.current_numbering = [0]

def format_numbering(self):
"""Return a string representation of the current numbering"""
return ".".join(str(n) for n in self.current_numbering)

def _inc_current_numbering(self, level):
"""Increase internal counter keeping track of numberings"""
if level > len(self.current_numbering):
self.current_numbering = self.current_numbering + [0] * (
level - len(self.current_numbering)
)
elif level < len(self.current_numbering):
self.current_numbering = self.current_numbering[:level]
self.current_numbering[level - 1] += 1

def _transform_markdown_line(self, line, resources):
"""Rewrites one markdown line, if needed"""
if m := re.match(r"^(?P<level>#+) (?P<heading>.*)", line):
level = len(m.group("level"))
self._inc_current_numbering(level)
old_heading = m.group("heading").strip()
new_heading = self.format_numbering() + " " + old_heading
return "#" * level + " " + new_heading

return line

def preprocess_cell(self, cell, resources, index):
"""Rewrites all the headings in the cell if it is markdown"""
if cell["cell_type"] == "markdown":
cell["source"] = "\n".join(
self._transform_markdown_line(line, resources)
for line in cell["source"].splitlines()
)

return cell, resources
86 changes: 86 additions & 0 deletions tests/preprocessors/test_numbered_headings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""
Module with tests for the Numbered Headings preprocessor.
"""

from nbformat import v4 as nbformat

from nbconvert.preprocessors.numbered_headings import NumberedHeadingsPreprocessor

from .base import PreprocessorTestsBase

MARKDOWN_1 = """
# Heading 1
## Sub-heading
some content
"""

MARKDOWN_1_POST = """
# 1 Heading 1
## 1.1 Sub-heading
some content
"""


MARKDOWN_2 = """
## Second sub-heading
# Another main heading
## Sub-heading
some more content
### Third heading
"""

MARKDOWN_2_POST = """
## 1.2 Second sub-heading
# 2 Another main heading
## 2.1 Sub-heading
some more content
### 2.1.1 Third heading
"""


class TestNumberedHeadings(PreprocessorTestsBase):
def build_notebook(self):
cells = [
nbformat.new_code_cell(source="$ e $", execution_count=1),
nbformat.new_markdown_cell(source=MARKDOWN_1),
nbformat.new_code_cell(source="$ e $", execution_count=1),
nbformat.new_markdown_cell(source=MARKDOWN_2),
]

return nbformat.new_notebook(cells=cells)

def build_preprocessor(self):
"""Make an instance of a preprocessor"""
preprocessor = NumberedHeadingsPreprocessor()
preprocessor.enabled = True
return preprocessor

def test_constructor(self):
"""Can a ClearOutputPreprocessor be constructed?"""
self.build_preprocessor()

def test_output(self):
"""Test the output of the NumberedHeadingsPreprocessor"""
nb = self.build_notebook()
res = self.build_resources()
preprocessor = self.build_preprocessor()
nb, res = preprocessor(nb, res)
print(nb.cells[1].source)
assert nb.cells[1].source.strip() == MARKDOWN_1_POST.strip()
assert nb.cells[3].source.strip() == MARKDOWN_2_POST.strip()

0 comments on commit 490bb79

Please sign in to comment.