diff --git a/CHANGELOG.md b/CHANGELOG.md index 935091a1..f10453f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Jupytext ChangeLog - The `rst2md` tests have been fixed by requiring `sphinx<8` ([#1266](https://github.com/mwouts/jupytext/issues/1266)) **Added** +- The Jupytext configuration has a new option `cell_id_to_title` that maps the cell id to a cell title ([#1263](https://github.com/mwouts/jupytext/issues/1263)) - Jupytext is now tested with Python 3.13 ([#1242](https://github.com/mwouts/jupytext/issues/1242)). Thanks to [Jerry James](https://github.com/jamesjer) for the suggested fixes! diff --git a/src/jupytext/combine.py b/src/jupytext/combine.py index 4bc56a2d..fafe9163 100644 --- a/src/jupytext/combine.py +++ b/src/jupytext/combine.py @@ -98,6 +98,7 @@ def combine_inputs_with_outputs(nb_source, nb_outputs, fmt=None): # Cell text is taken from the source notebook cell.source = source_cell.source + cell.id = source_cell.id # We also restore the cell metadata that has been filtered cell.metadata = restore_filtered_metadata( diff --git a/src/jupytext/config.py b/src/jupytext/config.py index 3b0269b0..6f1c05d3 100644 --- a/src/jupytext/config.py +++ b/src/jupytext/config.py @@ -122,6 +122,12 @@ class JupytextConfiguration(Configurable): config=True, ) + cell_id_to_title = Bool( + False, + help="Map the cell id to a cell title in text formats", + config=True, + ) + split_at_heading = Bool( False, help="Split markdown cells on headings (Markdown and R Markdown formats only)", diff --git a/src/jupytext/jupytext.py b/src/jupytext/jupytext.py index 80a9ed84..8585b8c0 100644 --- a/src/jupytext/jupytext.py +++ b/src/jupytext/jupytext.py @@ -175,6 +175,11 @@ def reads(self, s, **_): filtered_cells.append(cell) cells = filtered_cells + if self.config.cell_id_to_title: + for cell in cells: + if "title" in cell.metadata: + cell.id = cell.metadata.pop("title").replace(" ", "_") + return new_notebook(cells=cells, metadata=metadata) def filter_notebook(self, nb, metadata, preserve_cell_ids=False): @@ -283,6 +288,9 @@ def writes(self, nb, metadata=None, **kwargs): split_at_heading = self.fmt.get("split_at_heading", False) for cell in nb.cells: + if self.config.cell_id_to_title and hasattr(cell, "id"): + cell.metadata = {"title": cell.id, **cell.metadata} + if looking_for_first_markdown_cell and cell.cell_type == "markdown": cell.metadata.setdefault("cell_marker", '"""') looking_for_first_markdown_cell = False diff --git a/src/jupytext/version.py b/src/jupytext/version.py index 1887a67d..86a823e3 100644 --- a/src/jupytext/version.py +++ b/src/jupytext/version.py @@ -1,3 +1,3 @@ """Jupytext's version number""" -__version__ = "1.16.4" +__version__ = "1.16.5-dev" diff --git a/tests/functional/simple_notebooks/test_cell_id_to_title.py b/tests/functional/simple_notebooks/test_cell_id_to_title.py new file mode 100644 index 00000000..07f1a2ae --- /dev/null +++ b/tests/functional/simple_notebooks/test_cell_id_to_title.py @@ -0,0 +1,110 @@ +import pytest +from nbformat.v4.nbbase import new_code_cell, new_markdown_cell, new_notebook + +from jupytext.compare import compare, compare_notebooks +from jupytext.config import JupytextConfiguration +from jupytext.jupytext import reads, writes + + +@pytest.fixture +def notebook_with_custom_ids(python_notebook): + return new_notebook( + metadata=python_notebook.metadata, + cells=[ + new_markdown_cell( + id="first_markdown_cell", source="This is a markdown cell" + ), + new_code_cell(id="first_code_cell", source="1 + 1"), + ], + ) + + +@pytest.fixture +def py_light_with_custom_ids(): + return """# --- +# jupyter: +# kernelspec: +# display_name: Python 3 +# language: python +# name: python_kernel +# --- + +# + first_markdown_cell [markdown] +# This is a markdown cell + +# + first_code_cell +1 + 1 +""" + + +@pytest.fixture +def py_percent_with_custom_ids(): + return """# --- +# jupyter: +# kernelspec: +# display_name: Python 3 +# language: python +# name: python_kernel +# --- + +# %% first_markdown_cell [markdown] +# This is a markdown cell + +# %% first_code_cell +1 + 1 +""" + + +@pytest.fixture +def config(): + c = JupytextConfiguration() + c.cell_id_to_title = True + return c + + +def test_cell_id_to_py_light( + notebook_with_custom_ids, + py_light_with_custom_ids, + config, + no_jupytext_version_number, +): + compare( + writes(notebook_with_custom_ids, fmt="py:light", config=config), + py_light_with_custom_ids, + ) + + +def test_cell_id_from_py_light( + notebook_with_custom_ids, + py_light_with_custom_ids, + config, + no_jupytext_version_number, +): + compare_notebooks( + reads(py_light_with_custom_ids, fmt="py:light", config=config), + notebook_with_custom_ids, + ) + + +def test_cell_id_to_py_percent( + notebook_with_custom_ids, + py_percent_with_custom_ids, + config, + no_jupytext_version_number, +): + compare( + writes(notebook_with_custom_ids, fmt="py:percent", config=config), + py_percent_with_custom_ids, + ) + + +def test_cell_id_from_py_percent( + notebook_with_custom_ids, + py_percent_with_custom_ids, + config, + no_jupytext_version_number, +): + compare_notebooks( + reads(py_percent_with_custom_ids, fmt="py:percent", config=config), + notebook_with_custom_ids, + )