Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

adding myst init command #97

Merged
merged 1 commit into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/use/myst-notebooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ If **both** an `.ipynb` and a `.md` file exist in your book's folders, then
the `.md` file will take precedence!
```

### The Jupyter Book MyST CLI

Jupyter Book has a small CLI to provide common functionality for manipulating and
creating MyST markdown files that synchronize with Jupytext. To add Jupytext syntax
to a markdown file (that will tell Jupytext it is a MyST markdown file), run the
following command:

```bash
jupyter-book myst init mymarkdownfile.md --kernel kernelname
```

If you do not specify `--kernel`, then the default kernel will be used *if there is
only one available*. If there are multiple kernels available, you must specify one
manually.


## Structure of MyST notebooks

Let's take a look at the structure that Jupytext creates, which you may also use
Expand Down
22 changes: 20 additions & 2 deletions jupyter_book/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ..sphinx import build_sphinx
from ..toc import build_toc
from ..pdf import html_to_pdf
from ..utils import _message_box, _error
from ..utils import _message_box, _error, init_myst_file


@click.group()
Expand Down Expand Up @@ -91,7 +91,7 @@ def build(path_book, path_output, config, toc, warningiserror, build):
if exc:
_error(
"There was an error in building your book. "
"Look above for the error message.",
"Look above for the error message."
)
else:
# Builder-specific options
Expand Down Expand Up @@ -221,3 +221,21 @@ def toc(path, filename_split_char, skip_text, output_folder):
output_file.write_text(out_yaml)

_message_box(f"Table of Contents written to {output_file}")


@main.group()
def myst():
"""Manipulate MyST markdown files."""
pass


@myst.command()
@click.argument("path", nargs=-1, type=click.Path(exists=True, dir_okay=False))
@click.option(
"--kernel", help="The name of the Jupyter kernel to attach to this markdown file."
)
def init(path, kernel):
"""Add Jupytext metadata for your markdown file(s), with optional Kernel name.
"""
for ipath in path:
init_myst_file(ipath, kernel, verbose=True)
32 changes: 32 additions & 0 deletions jupyter_book/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from pathlib import Path
from subprocess import run, PIPE
import pytest
from jupyter_book.utils import init_myst_file


def test_myst_init(tmpdir):
"""Test adding myst metadata to text files."""
path = Path(tmpdir).joinpath("tmp.md").absolute()
text = "TEST"
with open(path, "w") as ff:
ff.write(text)
init_myst_file(path, kernel="python3")

# Make sure it runs properly. Default kernel should be python3
new_text = path.read_text()
assert "format_name: myst" in new_text
assert "TEST" == new_text.strip().split("\n")[-1]
assert "name: python3" in new_text

# Make sure the CLI works too
run(f"jb myst init {path} --kernel python3".split(), check=True, stdout=PIPE)

# Non-existent kernel
with pytest.raises(Exception) as err:
init_myst_file(path, kernel="blah")
assert "Did not find kernel: blah" in str(err)

# Missing file
with pytest.raises(Exception) as err:
init_myst_file(path.joinpath("MISSING"), kernel="python3")
assert "Markdown file not found:" in str(err)
50 changes: 50 additions & 0 deletions jupyter_book/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pathlib import Path
from textwrap import dedent
from jupyter_client.kernelspec import find_kernel_specs

SUPPORTED_FILE_SUFFIXES = [".ipynb", ".md", ".markdown", ".myst", ".Rmd", ".py"]

Expand Down Expand Up @@ -62,3 +63,52 @@ def _error(msg, kind=None):
kind = ValueError
box = _message_box(msg, color="red", doprint=False)
raise kind(box)


##############################################################################
# MyST + Jupytext


def init_myst_file(path, kernel, verbose=True):
"""Initialize a file with a Jupytext header that marks it as MyST markdown.

Parameters
----------
path : string
A path to a markdown file to be initialized for Jupytext
kernel : string
A kernel name to add to the markdown file. See a list of kernel names with
`jupyter kernelspec list`.
"""
try:
from jupytext.cli import jupytext
except ImportError:
raise ImportError(
"In order to use myst markdown features, " "please install jupytext first."
)
if not Path(path).exists():
raise FileNotFoundError(f"Markdown file not found: {path}")

kernels = list(find_kernel_specs().keys())
kernels_text = "\n".join(kernels)
if kernel is None:
if len(kernels) > 1:
_error(
"There are multiple kernel options, so you must give one manually."
" with `--kernel`\nPlease specify one of the following kernels.\n\n"
f"{kernels_text}"
)
else:
kernel = kernels[0]

if kernel not in kernels:
raise ValueError(
f"Did not find kernel: {kernel}\nPlease specify one of the "
f"installed kernels:\n\n{kernels_text}"
)

args = (str(path), "-q", "--set-kernel", kernel, "--set-formats", "myst")
jupytext(args)

if verbose:
print(f"Initialized file: {path}\nWith kernel: {kernel}")
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"beautifulsoup4",
"matplotlib",
"pytest-regressions",
"jupytext",
] + doc_reqs
setup(
name="jupyter-book",
Expand Down