Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Informative runtime error if myst is not installed #584

Merged
merged 4 commits into from
Aug 20, 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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
- Install Jupytext from source on MyBinder to avoid cache issues (#567)
- Skip the tests that execute a notebook on Windows to avoid timeout issues (#489)
- The `# %%` cell marker has the same indentation as the first line in the cell (#562)
- The `md:myst` and `md:pandoc` are always included in the Jupytext formats, and an informative runtime
error will occur if the required dependencies, resp. `myst-parser` and `pandoc`, are not installed. (#556)

**Fixed**
- Configured coverage targets in `codecov.yml`
- Only scripts can have an encoding comment, not Markdown or R Markdown files (#576)
- Support spaces in `--pipe` commands (#562)
- Spaces in `--pipe` commands are supported (#562)
- Use `>=` and `<` rather than `~=` in the `extras_require` of `setup.py` (#589)
- Bash commands starting with special characters are now correctly detected, thanks to Aaron Gokaslan (#587)
- MyST Markdown files are recognized as such even if `myst` is missing (#556)


1.5.2 (2020-07-21)
Expand Down
152 changes: 75 additions & 77 deletions jupytext/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from copy import copy
from tempfile import NamedTemporaryFile
from .jupytext import read, reads, write, writes
from .formats import NOTEBOOK_EXTENSIONS, JUPYTEXT_FORMATS
from .formats import JUPYTEXT_FORMATS
from .formats import _VALID_FORMAT_OPTIONS, _BINARY_FORMAT_OPTIONS, check_file_version
from .formats import (
long_form_one_format,
Expand Down Expand Up @@ -79,43 +79,39 @@ def parse_jupytext_args(args=None):
"Notebook is read from stdin when this argument is empty.",
nargs="*",
)
parser.add_argument(
"--from",
dest="input_format",
help="Jupytext format for the input(s). Inferred from the "
"file extension and content when missing.",
)
parser.add_argument(
"--pre-commit",
action="store_true",
help="Ignore the notebook argument, and instead apply Jupytext "
"on the notebooks found in the git index, which have an "
"extension that matches the (optional) --from argument.",
)
parser.add_argument(
"--from",
dest="input_format",
help="Jupytext format for the input(s). Inferred from the "
"file extension and content when missing.",
)
# Destination format & act on metadata
parser.add_argument(
"--to",
dest="output_format",
help=(
"Destination format: either 'notebook' (extension .ipynb), "
"'markdown' (.md), 'rmarkdown' (.Rmd), 'script', a supported "
"extension, or an explicit extension/format description "
"[[root_folder/][path/][prefix]/][suffix.]ext[:format_name], where "
+ "ext is one of {}, or auto,".format(
", ".join(ext[1:] for ext in NOTEBOOK_EXTENSIONS)
)
+ "format_name is optional, and can be {} for Markdown files, and ".format(
" or ".join(
"The destination format: 'ipynb', 'markdown' or 'script', or a file extension: "
"'md', 'Rmd', 'jl', 'py', 'R', ..., 'auto' (script extension matching the notebook language), "
"or a combination of an extension and a format name, e.g. {} ".format(
", ".join(
set(
fmt.format_name
"md:{}".format(fmt.format_name)
for fmt in JUPYTEXT_FORMATS
if fmt.extension == ".md"
)
)
)
+ "{} for scripts. ".format(
+ " or {}. ".format(
", ".join(
set(
fmt.format_name
"py:{}".format(fmt.format_name)
for fmt in JUPYTEXT_FORMATS
if fmt.extension == ".py"
)
Expand All @@ -124,49 +120,14 @@ def parse_jupytext_args(args=None):
+ "The default format for scripts is the 'light' format, "
"which uses few cell markers (none when possible). "
"Alternatively, a format compatible with many editors is the "
"'percent' format, which uses '# %%%%' as cell markers "
"'percent' format, which uses '# %%%%' as cell markers. "
"The main formats (markdown, light, percent) preserve "
"notebooks and text documents in a roundtrip. Use the "
"--test and and --test-strict commands to test the roundtrip on your files. "
"Read more about the available formats at "
"https://jupytext.readthedocs.io/en/latest/formats.html"
),
)
parser.add_argument(
"--format-options",
"--opt",
action="append",
help="Set format options with e.g. "
"'--opt comment_magics=true' or '--opt notebook_metadata_filter=-kernelspec'.",
)
parser.add_argument(
"--set-formats",
type=str,
help="Set jupytext.formats metadata to the given value. Use this to "
"activate pairing on a notebook, with e.g. '--set-formats ipynb,py:light'. "
"The --set-formats option also triggers the creation/update of all paired files",
)
parser.add_argument(
"--set-kernel",
"-k",
type=str,
help="Set the kernel with the given name on the notebook. "
"Use '--set-kernel -' to set a kernel matching the current "
"environment on Python notebooks, and matching the notebook "
"language otherwise (get the list of available kernels with "
"'jupyter kernelspec list')",
)
parser.add_argument(
"--update-metadata",
default={},
type=json.loads,
help="Update the notebook metadata with the desired dictionary. "
"Argument must be given in JSON format. For instance, if you "
"want to activate a pairing in the generated file, use e.g. "
"""--update-metadata '{"jupytext":{"formats":"ipynb,py:light"}}' """
"See also the --opt and --set-formats options for other ways "
"to operate on the Jupytext metadata.",
)

# Destination file
parser.add_argument(
Expand All @@ -181,31 +142,51 @@ def parse_jupytext_args(args=None):
"--update",
action="store_true",
help="Preserve the output cells when the destination "
"notebook is a .ipynb file that already exists",
"notebook is an .ipynb file that already exists",
)

parser.add_argument(
"--set-formats",
type=str,
help="Turn the notebook or text document to one or more alternative representations "
"with e.g. '--set-formats ipynb,py:light'. "
"The --set-formats option also triggers the creation/update of all paired files",
)

# Action: convert(default)/version/list paired paths/sync/apply/test
action = parser.add_mutually_exclusive_group()
action.add_argument(
"--version",
"-v",
"--sync",
"-s",
help="Synchronize the content of the paired representations of "
"the given notebook. Input cells are taken from the file that "
"was last modified, and outputs are read from the ipynb file, "
"if present.",
action="store_true",
help="Show jupytext's version number and exit",
)
action.add_argument(
"--paired-paths",
"-p",
help="List the locations of the alternative representations for this notebook.",
action="store_true",
)
action.add_argument(
"--sync",
"-s",
help="Synchronize the content of the paired representations of "
"the given notebook. Input cells are taken from the file that "
"was last modified, and outputs are read from the ipynb file, "
"if present.",
action="store_true",
parser.add_argument(
"--format-options",
"--opt",
action="append",
help="Set format options with e.g. "
"'--opt comment_magics=true' or '--opt notebook_metadata_filter=-kernelspec'.",
)
parser.add_argument(
"--update-metadata",
default={},
type=json.loads,
help="Update the notebook metadata with the desired dictionary. "
"Argument must be given in JSON format. For instance, if you "
"want to activate a pairing in the generated file, use e.g. "
"""--update-metadata '{"jupytext":{"formats":"ipynb,py:light"}}' """
"See also the --opt and --set-formats options for other ways "
"to operate on the Jupytext metadata.",
)
action.add_argument(
"--warn-only",
Expand All @@ -217,13 +198,13 @@ def parse_jupytext_args(args=None):
action.add_argument(
"--test",
action="store_true",
help="Test that notebook is stable under a round trip conversion, "
help="Test that the notebook is stable under a round trip conversion, "
"up to the expected changes",
)
action.add_argument(
"--test-strict",
action="store_true",
help="Test that notebook is strictly stable under a round trip conversion",
help="Test that the notebook is strictly stable under a round trip conversion",
)
parser.add_argument(
"--stop",
Expand Down Expand Up @@ -270,6 +251,16 @@ def parse_jupytext_args(args=None):
)

# Execute the notebook
parser.add_argument(
"--set-kernel",
"-k",
type=str,
help="Set the kernel with the given name on the notebook. "
"Use '--set-kernel -' to set a kernel matching the current "
"environment on Python notebooks, and matching the notebook "
"language otherwise (get the list of available kernels with "
"'jupyter kernelspec list')",
)
parser.add_argument(
"--execute",
action="store_true",
Expand All @@ -284,6 +275,13 @@ def parse_jupytext_args(args=None):
help="Quiet mode: do not comment about files being updated or created",
)

action.add_argument(
"--version",
"-v",
action="store_true",
help="Show jupytext's version number and exit",
)

return parser.parse_args(args)


Expand Down Expand Up @@ -329,14 +327,14 @@ def log(text):

if (
(args.test or args.test_strict)
and not args.to
and not args.output_format
and not args.output
and not args.sync
):
raise ValueError("Please provide one of --to, --output or --sync")

if (
not args.to
not args.output_format
and not args.output
and not args.sync
and not args.pipe
Expand All @@ -357,9 +355,9 @@ def log(text):
if args.input_format:
args.input_format = long_form_one_format(args.input_format)

if args.to:
args.to = long_form_one_format(args.to)
set_format_options(args.to, args.format_options)
if args.output_format:
args.output_format = long_form_one_format(args.output_format)
set_format_options(args.output_format, args.format_options)

# Wildcard extension on Windows #202
notebooks = []
Expand Down Expand Up @@ -391,11 +389,11 @@ def jupytext_single_file(nb_file, args, log):

nb_dest = args.output or (
None
if not args.to
if not args.output_format
else (
"-"
if nb_file == "-"
else full_path(base_path(nb_file, args.input_format), args.to)
else full_path(base_path(nb_file, args.input_format), args.output_format)
)
)

Expand Down Expand Up @@ -441,7 +439,7 @@ def jupytext_single_file(nb_file, args, log):
notebook.metadata.setdefault("jupytext", {})["formats"] = default_formats

# Compute actual extension when using script/auto, and update nb_dest if necessary
dest_fmt = args.to
dest_fmt = args.output_format
if dest_fmt and dest_fmt["extension"] == ".auto":
dest_fmt = check_auto_ext(dest_fmt, notebook.metadata, "--to")
if not args.output and nb_file != "-":
Expand Down
51 changes: 28 additions & 23 deletions jupytext/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import os
import re
import yaml
import warnings
import nbformat
from .header import header_to_metadata_and_cell, insert_or_test_version_number
Expand All @@ -30,7 +31,7 @@
from .metadata_filter import metadata_filter_as_string
from .stringparser import StringParser
from .languages import _SCRIPT_EXTENSIONS, _COMMENT_CHARS, same_language
from .pandoc import pandoc_version, is_pandoc_available
from .pandoc import pandoc_version
from .magics import is_magic
from .myst import (
MYST_FORMAT_NAME,
Expand Down Expand Up @@ -191,36 +192,28 @@ def __init__(
cell_exporter_class=SphinxGalleryCellExporter,
# Version 1.0 on 2018-09-22 - jupytext v0.7.0rc0 : Initial version
current_version_number="1.1",
)
]
)

if is_pandoc_available():
JUPYTEXT_FORMATS.append(
),
NotebookFormatDescription(
format_name="pandoc",
extension=".md",
header_prefix="",
cell_reader_class=None,
cell_exporter_class=None,
current_version_number=pandoc_version(),
),
]
+ [
NotebookFormatDescription(
format_name=MYST_FORMAT_NAME,
extension=ext,
header_prefix="",
cell_reader_class=None,
cell_exporter_class=None,
current_version_number=myst_version(),
)
)

if is_myst_available():
JUPYTEXT_FORMATS.extend(
[
NotebookFormatDescription(
format_name=MYST_FORMAT_NAME,
extension=ext,
header_prefix="",
cell_reader_class=None,
cell_exporter_class=None,
current_version_number=myst_version(),
)
for ext in myst_extensions()
]
)
for ext in myst_extensions()
]
)

NOTEBOOK_EXTENSIONS = list(
dict.fromkeys([".ipynb"] + [fmt.extension for fmt in JUPYTEXT_FORMATS])
Expand Down Expand Up @@ -269,6 +262,18 @@ def read_metadata(text, ext):
if ext in [".r", ".R"] and not metadata:
metadata, _, _, _ = header_to_metadata_and_cell(lines, "#'", ext)

# MyST has the metadata at the root level
if not metadata and ext in myst_extensions() and text.startswith("---"):
for header in yaml.safe_load_all(text):
if (
header.get("jupytext", {})
.get("text_representation", {})
.get("format_name")
== "myst"
):
return header
return metadata

return metadata


Expand Down
Loading