diff --git a/CHANGELOG.md b/CHANGELOG.md index 732c405f1..66fba58cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ **Added** - Activated GitHub code scanning alerts +- New option `hide_notebook_metadata` to encapsulate the notebook metadata in an HTML comment (#527) **Changed** - Install Jupytext from source on MyBinder to avoid cache issues (#567) diff --git a/docs/config.md b/docs/config.md index 9745d3f5e..002b44e5f 100644 --- a/docs/config.md +++ b/docs/config.md @@ -105,6 +105,8 @@ It is possible to filter nested metadata. For example, if you want to preserve t default_notebook_metadata_filter = "-jupytext.text_representation.jupytext_version" ``` +Finally, to hide the notebook metadata in an HTML comment in Markdown files, use the option `hide_notebook_metadata`. + ### More options There are a couple more options available - please have a look at the `JupytextConfiguration` class in [config.py](https://github.com/mwouts/jupytext/blob/master/jupytext/config.py). diff --git a/jupytext/cli.py b/jupytext/cli.py index 66e208f2c..5c8b7c52d 100644 --- a/jupytext/cli.py +++ b/jupytext/cli.py @@ -409,9 +409,14 @@ def jupytext_single_file(nb_file, args, log): # I. ### Read the notebook ### fmt = copy(args.input_format) or {} - if config: - config.set_default_format_options(fmt) - set_format_options(fmt, args.format_options) + if not fmt: + ext = os.path.splitext(nb_file)[1] + if ext: + fmt = {"extension": ext} + if fmt: + if config: + config.set_default_format_options(fmt) + set_format_options(fmt, args.format_options) log( "[jupytext] Reading {}{}".format( nb_file if nb_file != "-" else "stdin", @@ -422,19 +427,12 @@ def jupytext_single_file(nb_file, args, log): ) notebook = read(nb_file, fmt=fmt) - if not fmt: + if "extension" in fmt and "format_name" not in fmt: text_representation = notebook.metadata.get("jupytext", {}).get( "text_representation", {} ) - ext = os.path.splitext(nb_file)[1] - if text_representation.get("extension") == ext: - fmt = { - key: text_representation[key] - for key in text_representation - if key in ["extension", "format_name"] - } - elif ext: - fmt = {"extension": ext} + if text_representation.get("extension") == fmt["extension"]: + fmt["format_name"] = text_representation["format_name"] if config and "formats" not in notebook.metadata.get("jupytext", {}): default_formats = config.default_formats(nb_file) diff --git a/jupytext/config.py b/jupytext/config.py index 4ecd69fa9..7f005de46 100644 --- a/jupytext/config.py +++ b/jupytext/config.py @@ -62,14 +62,21 @@ class JupytextConfiguration(Configurable): default_notebook_metadata_filter = Unicode( u"", - help="Cell metadata that should be save in the text representations. " + help="Notebook metadata that should be save in the text representations. " "Examples: 'all', '-all', 'widgets,nteract', 'kernelspec,jupytext-all'", config=True, ) + hide_notebook_metadata = Enum( + values=[True, False], + allow_none=True, + help="Should the notebook metadata be wrapped into an HTML comment in the Markdown format?", + config=True, + ) + default_cell_metadata_filter = Unicode( u"", - help="Notebook metadata that should be saved in the text representations. " + help="Cell metadata that should be saved in the text representations. " "Examples: 'all', 'hide_input,hide_output'", config=True, ) @@ -124,6 +131,10 @@ def set_default_format_options(self, format_options, read=False): format_options.setdefault( "cell_metadata_filter", self.default_cell_metadata_filter ) + if self.hide_notebook_metadata is not None: + format_options.setdefault( + "hide_notebook_metadata", self.hide_notebook_metadata + ) if self.comment_magics is not None: format_options.setdefault("comment_magics", self.comment_magics) if self.split_at_heading: diff --git a/jupytext/formats.py b/jupytext/formats.py index 026092d6e..0bada4959 100644 --- a/jupytext/formats.py +++ b/jupytext/formats.py @@ -692,6 +692,7 @@ def short_form_multiple_formats(jupytext_formats): _VALID_FORMAT_INFO = ["extension", "format_name", "suffix", "prefix"] _BINARY_FORMAT_OPTIONS = [ "comment_magics", + "hide_notebook_metadata", "split_at_heading", "rst2md", "cell_metadata_json", diff --git a/jupytext/header.py b/jupytext/header.py index ddabbbd3d..91510714b 100644 --- a/jupytext/header.py +++ b/jupytext/header.py @@ -116,6 +116,12 @@ def metadata_and_cell_to_header(notebook, metadata, text_format, ext): if header: header = ["---"] + header + ["---"] + if ( + metadata.get("jupytext", {}).get("hide_notebook_metadata", False) + and text_format.format_name == "markdown" + ): + header = [""] + return comment_lines(header, text_format.header_prefix), lines_to_next_cell @@ -142,10 +148,13 @@ def header_to_metadata_and_cell(lines, header_prefix, ext=None): header = [] jupyter = [] - injupyter = False + in_jupyter = False + in_html_div = False + + start = 0 + started = False ended = False metadata = {} - start = 0 i = -1 comment = "#" if header_prefix == "#'" else header_prefix @@ -167,27 +176,37 @@ def header_to_metadata_and_cell(lines, header_prefix, ext=None): metadata.setdefault("jupytext", {})["encoding"] = line start = i + 1 continue - if not line.startswith(header_prefix): break + if not comment: + if line.strip().startswith("" in line: + break + if not started and not line.strip(): continue - break - if i > start and _HEADER_RE.match(line): - ended = True - break + line = uncomment_line(line, header_prefix) + if _HEADER_RE.match(line): + if not started: + started = True + continue + else: + ended = True + if in_html_div: + continue + break if _JUPYTER_RE.match(line): - injupyter = True + in_jupyter = True elif line and not _LEFTSPACE_RE.match(line): - injupyter = False + in_jupyter = False - if injupyter: + if in_jupyter: jupyter.append(line) else: header.append(line) diff --git a/tests/test_cli_config.py b/tests/test_cli_config.py index decb3a035..c83fea7ee 100644 --- a/tests/test_cli_config.py +++ b/tests/test_cli_config.py @@ -3,6 +3,7 @@ from jupytext.cli import jupytext from jupytext.jupytext import read, write from jupytext.header import header_to_metadata_and_cell +from jupytext.compare import compare def test_default_jupytext_formats(tmpdir): @@ -89,3 +90,38 @@ def test_save_using_preferred_and_default_format(config, tmpdir): # read py file nb_py = read(str(tmp_py)) assert nb_py.metadata["jupytext"]["text_representation"]["format_name"] == "percent" + + +def test_hide_notebook_metadata(tmpdir, no_jupytext_version_number): + tmpdir.join(".jupytext").write("hide_notebook_metadata = true") + tmp_ipynb = tmpdir.join("notebook.ipynb") + tmp_md = tmpdir.join("notebook.md") + + nb = new_notebook( + cells=[new_code_cell("1 + 1")], metadata={"jupytext": {"formats": "ipynb,md"}} + ) + + write(nb, str(tmp_ipynb)) + jupytext([str(tmp_ipynb), "--sync"]) + + with open(str(tmp_md)) as stream: + text_md = stream.read() + + compare( + text_md, + """ + +```python +1 + 1 +``` +""", + ) diff --git a/tests/test_header.py b/tests/test_header.py index 9801e0bed..8977ba05a 100644 --- a/tests/test_header.py +++ b/tests/test_header.py @@ -148,3 +148,41 @@ def test_multiline_metadata( compare(actual, markdown) nb2 = jupytext.reads(markdown, ".md") compare(nb2, notebook) + + +def test_header_in_html_comment(): + text = """ +""" + lines = text.splitlines() + metadata, _, cell, _ = header_to_metadata_and_cell(lines, "") + + assert metadata == {"title": "Sample header"} + assert cell is None + + +def test_header_to_html_comment(no_jupytext_version_number): + metadata = {"jupytext": {"mainlanguage": "python", "hide_notebook_metadata": True}} + nb = new_notebook(metadata=metadata, cells=[]) + header, lines_to_next_cell = metadata_and_cell_to_header( + nb, metadata, get_format_implementation(".md"), ".md" + ) + compare( + "\n".join(header), + """""", + ) diff --git a/tests/test_read_simple_markdown.py b/tests/test_read_simple_markdown.py index ad0966933..728713faa 100644 --- a/tests/test_read_simple_markdown.py +++ b/tests/test_read_simple_markdown.py @@ -916,3 +916,37 @@ def test_custom_metadata( compare(md2, md) nb2 = jupytext.reads(md, "md") compare_notebooks(nb2, nb) + + +def test_hide_notebook_metadata( + no_jupytext_version_number, + nb=new_notebook( + metadata={ + "jupytext": {"hide_notebook_metadata": True}, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3", + }, + } + ), + md=""" +""", +): + """Test the hide_notebook_metadata option""" + md2 = jupytext.writes(nb, "md") + compare(md2, md) + nb2 = jupytext.reads(md, "md") + compare_notebooks(nb2, nb)