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

Commit

Permalink
Merge pull request #97 from ExecutableBookProject/myst_init
Browse files Browse the repository at this point in the history
adding myst init command
  • Loading branch information
choldgraf authored Apr 15, 2020
2 parents 00fbc42 + 833343a commit 2558c7a
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 2 deletions.
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

0 comments on commit 2558c7a

Please sign in to comment.