Skip to content

Commit 6eb309d

Browse files
authored
Merge pull request #147 from foster999/fix/143/windows-ci
Fix Windows CI
2 parents 1d22c14 + 76f184a commit 6eb309d

File tree

8 files changed

+66
-52
lines changed

8 files changed

+66
-52
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88

99
strategy:
1010
matrix:
11-
python-version: [3.5, 3.6, 3.7, 3.8]
11+
python-version: [3.6, 3.7, 3.8]
1212
os: [ubuntu-latest]
1313
include:
1414
- os: windows-latest

jupyter_sphinx/__init__.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from sphinx.util.fileutil import copy_asset
99
from sphinx.errors import ExtensionError
1010
from IPython.lib.lexers import IPythonTracebackLexer, IPython3Lexer
11+
from pathlib import Path
1112

1213
from .ast import (
1314
JupyterCell,
@@ -121,18 +122,20 @@ def build_finished(app, env):
121122
if app.builder.format != "html":
122123
return
123124

125+
module_dir = Path(__file__).parent
126+
outdir = Path(app.outdir)
127+
124128
# Copy stylesheet
125-
src = os.path.join(os.path.dirname(__file__), "css")
126-
dst = os.path.join(app.outdir, "_static")
129+
src = module_dir / "css"
130+
dst = outdir / "_static"
127131
copy_asset(src, dst)
128132

129133
thebe_config = app.config.jupyter_sphinx_thebelab_config
130134
if not thebe_config:
131135
return
132136

133137
# Copy all thebelab related assets
134-
src = os.path.join(os.path.dirname(__file__), "thebelab")
135-
dst = os.path.join(app.outdir, "_static")
138+
src = module_dir / "thebelab"
136139
copy_asset(src, dst)
137140

138141

jupyter_sphinx/ast.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os
44
import json
5+
from pathlib import Path
56

67
import docutils
78
from docutils.parsers.rst import Directive, directives
@@ -101,7 +102,7 @@ def run(self):
101102
location=location,
102103
)
103104
try:
104-
with open(filename) as f:
105+
with Path(filename).open() as f:
105106
content = [line.rstrip() for line in f.readlines()]
106107
except (IOError, OSError):
107108
raise IOError("File {} not found or reading it failed".format(filename))
@@ -228,7 +229,7 @@ def html(self):
228229
)
229230

230231

231-
def cell_output_to_nodes(outputs, data_priority, write_stderr, dir, thebe_config):
232+
def cell_output_to_nodes(outputs, data_priority, write_stderr, out_dir, thebe_config):
232233
"""Convert a jupyter cell with outputs and filenames to doctree nodes.
233234
234235
Parameters
@@ -238,7 +239,7 @@ def cell_output_to_nodes(outputs, data_priority, write_stderr, dir, thebe_config
238239
Which media types to prioritize.
239240
write_stderr : bool
240241
If True include stderr in cell output
241-
dir : string
242+
out_dir : string
242243
Sphinx "absolute path" to the output folder, so it is a relative path
243244
to the source folder prefixed with ``/``.
244245
thebe_config: dict
@@ -304,19 +305,17 @@ def cell_output_to_nodes(outputs, data_priority, write_stderr, dir, thebe_config
304305
continue
305306
data = output["data"][mime_type]
306307
if mime_type.startswith("image"):
308+
file_path = Path(output.metadata["filenames"][mime_type])
309+
out_dir = Path(out_dir)
307310
# Sphinx treats absolute paths as being rooted at the source
308311
# directory, so make a relative path, which Sphinx treats
309312
# as being relative to the current working directory.
310-
filename = os.path.basename(output.metadata["filenames"][mime_type])
313+
filename = file_path.name
311314

312-
# checks if file dir path is inside a subdir of dir
313-
filedir = os.path.dirname(output.metadata["filenames"][mime_type])
314-
subpaths = filedir.split(dir)
315-
if subpaths and len(subpaths) > 1:
316-
subpath = subpaths[1]
317-
dir += subpath
315+
if out_dir in file_path.parents:
316+
out_dir = file_path.parent
318317

319-
uri = os.path.join(dir, filename)
318+
uri = (out_dir / filename).as_posix()
320319
to_add.append(docutils.nodes.image(uri=uri))
321320
elif mime_type == "text/html":
322321
to_add.append(

jupyter_sphinx/execute.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Execution and managing kernels."""
22

33
import os
4+
from pathlib import Path
45

56
from sphinx.transforms import SphinxTransform
67
from sphinx.errors import ExtensionError
@@ -86,8 +87,9 @@ class ExecuteJupyterCells(SphinxTransform):
8687

8788
def apply(self):
8889
doctree = self.document
89-
doc_relpath = os.path.dirname(self.env.docname) # relative to src dir
90-
docname = os.path.basename(self.env.docname)
90+
docname_path = Path(self.env.docname)
91+
doc_dir_relpath = docname_path.parent # relative to src dir
92+
docname = docname_path.name
9193
default_kernel = self.config.jupyter_execute_default_kernel
9294
default_names = default_notebook_names(docname)
9395
thebe_config = self.config.jupyter_sphinx_thebelab_config
@@ -105,7 +107,7 @@ def apply(self):
105107
add_thebelab_library(doctree, self.env)
106108

107109
js.logger.info("executing {}".format(docname))
108-
output_dir = os.path.join(output_directory(self.env), doc_relpath)
110+
output_dir = Path(output_directory(self.env)) / doc_dir_relpath
109111

110112
# Start new notebook whenever a JupyterKernelNode is encountered
111113
jupyter_nodes = (JupyterCellNode, JupyterKernelNode)
@@ -205,7 +207,7 @@ def apply(self):
205207
# Write certain cell outputs (e.g. images) to separate files, and
206208
# modify the metadata of the associated cells in 'notebook' to
207209
# include the path to the output file.
208-
write_notebook_output(notebook, output_dir, file_name, self.env.docname)
210+
write_notebook_output(notebook, str(output_dir), file_name, self.env.docname)
209211

210212
try:
211213
cm_language = notebook.metadata.language_info.codemirror_mode.name
@@ -265,9 +267,10 @@ def write_notebook_output(notebook, output_dir, notebook_name, location=None):
265267
location=location,
266268
)
267269
contents = "\n\n".join(cell.source for cell in notebook.cells)
268-
with open(os.path.join(output_dir, notebook_name + ext), "w",
269-
encoding = "utf8") as f:
270-
f.write(contents)
270+
271+
notebook_file = notebook_name + ext
272+
output_dir = Path(output_dir)
273+
(output_dir / notebook_file).write_text(contents, encoding = "utf8")
271274

272275

273276
def contains_widgets(notebook):

jupyter_sphinx/thebelab.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import docutils
55
from docutils.parsers.rst import Directive
6+
from pathlib import Path
67

78
import jupyter_sphinx as js
89

@@ -86,23 +87,23 @@ def add_thebelab_library(doctree, env):
8687
if isinstance(thebe_config, dict):
8788
pass
8889
elif isinstance(thebe_config, str):
89-
if os.path.isabs(thebe_config):
90+
thebe_config = Path(thebe_config)
91+
if thebe_config.is_absolute():
9092
filename = thebe_config
9193
else:
92-
filename = os.path.join(os.path.abspath(env.app.srcdir), thebe_config)
94+
filename = Path(env.app.srcdir).resolve() / thebe_config
9395

94-
if not os.path.exists(filename):
96+
if not filename.exists():
9597
js.logger.warning("The supplied thebelab configuration file does not exist")
9698
return
9799

98-
with open(filename, "r") as config_file:
99-
try:
100-
thebe_config = json.load(config_file)
101-
except ValueError:
102-
js.logger.warning(
103-
"The supplied thebelab configuration file is not in JSON format."
104-
)
105-
return
100+
try:
101+
thebe_config = json.loads(filename.read_text())
102+
except ValueError:
103+
js.logger.warning(
104+
"The supplied thebelab configuration file is not in JSON format."
105+
)
106+
return
106107
else:
107108
js.logger.warning(
108109
"The supplied thebelab configuration should be either a filename or a dictionary."

jupyter_sphinx/utils.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from sphinx.errors import ExtensionError
55
import nbformat
66
from jupyter_client.kernelspec import get_kernel_spec, NoSuchKernel
7+
from pathlib import Path
78

89

910
def blank_nb(kernel_name):
@@ -74,12 +75,14 @@ def sphinx_abs_dir(env, *paths):
7475
# output_directory / jupyter_execute / path relative to source directory
7576
# Sphinx expects download links relative to source file or relative to
7677
# source dir and prepended with '/'. We use the latter option.
77-
return "/" + os.path.relpath(
78-
os.path.abspath(
79-
os.path.join(output_directory(env), os.path.dirname(env.docname), *paths)
80-
),
81-
os.path.abspath(env.app.srcdir),
82-
)
78+
out_path = (output_directory(env) / Path(env.docname).parent / Path(*paths)).resolve()
79+
80+
if os.name == "nt":
81+
# Can't get relative path between drives on Windows
82+
return out_path.as_posix()
83+
84+
# Path().relative_to() doesn't work when not a direct subpath
85+
return "/" + os.path.relpath(out_path, env.app.srcdir)
8386

8487

8588
def output_directory(env):
@@ -90,6 +93,4 @@ def output_directory(env):
9093

9194
# Note: we are using an implicit fact that sphinx output directories are
9295
# direct subfolders of the build directory.
93-
return os.path.abspath(
94-
os.path.join(env.app.outdir, os.path.pardir, "jupyter_execute")
95-
)
96+
return (Path(env.app.outdir) / os.path.pardir / "jupyter_execute").resolve()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@
3434
"nbconvert>=5.5",
3535
"nbformat",
3636
],
37-
python_requires=">= 3.5",
37+
python_requires=">= 3.6",
3838
package_data={"jupyter_sphinx": ["thebelab/*", "css/*"]},
3939
)

tests/test_execute.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import sys
55
from io import StringIO
66
from unittest.mock import Mock
7+
from pathlib import Path
78

89
from sphinx.addnodes import download_reference
910
from sphinx.testing.util import assert_node, SphinxTestApp, path
@@ -34,16 +35,17 @@ def doctree():
3435
def doctree(
3536
source, config=None, return_warnings=False, entrypoint="jupyter_sphinx"
3637
):
37-
src_dir = tempfile.mkdtemp()
38+
src_dir = Path(tempfile.mkdtemp())
3839
source_trees.append(src_dir)
39-
with open(os.path.join(src_dir, "conf.py"), "w", encoding = "utf8") as f:
40-
f.write("extensions = ['%s']" % entrypoint)
41-
if config is not None:
42-
f.write("\n" + config)
43-
with open(os.path.join(src_dir, "contents.rst"), "w", encoding = "utf8") as f:
44-
f.write(source)
40+
41+
conf_contents = "extensions = ['%s']" % entrypoint
42+
if config is not None:
43+
conf_contents += "\n" + config
44+
(src_dir / "conf.py").write_text(conf_contents, encoding = "utf8")
45+
(src_dir / "contents.rst").write_text(source, encoding = "utf8")
46+
4547
warnings = StringIO()
46-
app = SphinxTestApp(srcdir=path(src_dir), status=StringIO(), warning=warnings)
48+
app = SphinxTestApp(srcdir=path(src_dir.as_posix()), status=StringIO(), warning=warnings)
4749
apps.append(app)
4850
app.build()
4951

@@ -586,6 +588,11 @@ def test_download_role(text, reftarget, caption, tmp_path):
586588
}
587589
mock_inliner.configure_mock(**config)
588590
ret, msg = role('jupyter-download:notebook', text, text, 0, mock_inliner)
591+
592+
if os.name == "nt":
593+
# Get equivalent abs path for Windows
594+
reftarget = (Path(tmp_path) / reftarget[1:]).resolve().as_posix()
595+
589596
assert_node(ret[0], [download_reference], reftarget=reftarget)
590597
assert_node(ret[0][0], [literal, caption])
591598
assert msg == []

0 commit comments

Comments
 (0)