1"""Module containing a preprocessor that removes metadata from code cells"""
2
3# Copyright (c) IPython Development Team.
4# Distributed under the terms of the Modified BSD License.
5
6from traitlets import Bool, Set
7
8from .base import Preprocessor
9
10
11class ClearMetadataPreprocessor(Preprocessor):
12 """
13 Removes all the metadata from all code cells in a notebook.
14 """
15
16 clear_cell_metadata = Bool(
17 True,
18 help=("Flag to choose if cell metadata is to be cleared in addition to notebook metadata."),
19 ).tag(config=True)
20 clear_notebook_metadata = Bool(
21 True,
22 help=("Flag to choose if notebook metadata is to be cleared in addition to cell metadata."),
23 ).tag(config=True)
24 preserve_nb_metadata_mask = Set(
25 [("language_info", "name")],
26 help=(
27 "Indicates the key paths to preserve when deleting metadata "
28 "across both cells and notebook metadata fields. Tuples of "
29 "keys can be passed to preserved specific nested values"
30 ),
31 ).tag(config=True)
32 preserve_cell_metadata_mask = Set(
33 help=(
34 "Indicates the key paths to preserve when deleting metadata "
35 "across both cells and notebook metadata fields. Tuples of "
36 "keys can be passed to preserved specific nested values"
37 )
38 ).tag(config=True)
39
40 def current_key(self, mask_key):
41 """Get the current key for a mask key."""
42 if isinstance(mask_key, str):
43 return mask_key
44 if len(mask_key) == 0:
45 # Safeguard
46 return None
47 return mask_key[0]
48
49 def current_mask(self, mask):
50 """Get the current mask for a mask."""
51 return {self.current_key(k) for k in mask if self.current_key(k) is not None}
52
53 def nested_masks(self, mask):
54 """Get the nested masks for a mask."""
55 return {
56 self.current_key(k[0]): k[1:]
57 for k in mask
58 if k and not isinstance(k, str) and len(k) > 1
59 }
60
61 def nested_filter(self, items, mask):
62 """Get the nested filter for items given a mask."""
63 keep_current = self.current_mask(mask)
64 keep_nested_lookup = self.nested_masks(mask)
65 for k, v in items:
66 keep_nested = keep_nested_lookup.get(k)
67 if k in keep_current:
68 if keep_nested is not None:
69 if isinstance(v, dict):
70 yield k, dict(self.nested_filter(v.items(), keep_nested))
71 else:
72 yield k, v
73
74 def preprocess_cell(self, cell, resources, cell_index):
75 """
76 All the code cells are returned with an empty metadata field.
77 """
78 if self.clear_cell_metadata and cell.cell_type == "code": # noqa: SIM102
79 # Remove metadata
80 if "metadata" in cell:
81 cell.metadata = dict(
82 self.nested_filter(cell.metadata.items(), self.preserve_cell_metadata_mask)
83 )
84 return cell, resources
85
86 def preprocess(self, nb, resources):
87 """
88 Preprocessing to apply on each notebook.
89
90 Must return modified nb, resources.
91
92 Parameters
93 ----------
94 nb : NotebookNode
95 Notebook being converted
96 resources : dictionary
97 Additional resources used in the conversion process. Allows
98 preprocessors to pass variables into the Jinja engine.
99 """
100 nb, resources = super().preprocess(nb, resources)
101 if self.clear_notebook_metadata and "metadata" in nb:
102 nb.metadata = dict(
103 self.nested_filter(nb.metadata.items(), self.preserve_nb_metadata_mask)
104 )
105 return nb, resources