-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from jchodera/docs-skeleton
Basic docs skeleton
- Loading branch information
Showing
4 changed files
with
320 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import os | ||
import shutil | ||
import glob | ||
|
||
from sphinx.util.compat import Directive | ||
from docutils import nodes | ||
from docutils.parsers.rst import directives | ||
from IPython.nbconvert import html, python | ||
|
||
from runipy.notebook_runner import NotebookRunner | ||
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__)) | ||
IPYTHON_NOTEBOOK_DIR = "%s/../../examples" % HERE | ||
|
||
class NotebookDirective(Directive): | ||
"""Insert an evaluated notebook into a document | ||
This uses runipy and nbconvert to transform a path to an unevaluated notebook | ||
into html suitable for embedding in a Sphinx document. | ||
""" | ||
required_arguments = 1 | ||
optional_arguments = 1 | ||
option_spec = {'skip_exceptions' : directives.flag} | ||
|
||
def run(self): | ||
# check if raw html is supported | ||
if not self.state.document.settings.raw_enabled: | ||
raise self.warning('"%s" directive disabled.' % self.name) | ||
|
||
# get path to notebook | ||
source_dir = os.path.dirname( | ||
os.path.abspath(self.state.document.current_source)) | ||
nb_basename = os.path.basename(self.arguments[0]) | ||
rst_file = self.state_machine.document.attributes['source'] | ||
rst_dir = os.path.abspath(os.path.dirname(rst_file)) | ||
nb_abs_path = os.path.join(IPYTHON_NOTEBOOK_DIR, nb_basename) | ||
|
||
# Move files around. | ||
rel_dir = os.path.relpath(rst_dir, setup.confdir) | ||
rel_path = os.path.join(rel_dir, nb_basename) | ||
dest_dir = os.path.join(setup.app.builder.outdir, rel_dir) | ||
dest_path = os.path.join(dest_dir, nb_basename) | ||
|
||
if not os.path.exists(dest_dir): | ||
os.makedirs(dest_dir) | ||
|
||
# Copy unevaluated script | ||
try: | ||
shutil.copyfile(nb_abs_path, dest_path) | ||
except IOError: | ||
raise RuntimeError("Unable to copy notebook to build destination. %s -> %s" % (nb_abs_path, dest_path)) | ||
|
||
dest_path_eval = dest_path.replace('.ipynb', '_evaluated.ipynb') | ||
dest_path_script = dest_path.replace('.ipynb', '.py') | ||
rel_path_eval = nb_basename.replace('.ipynb', '_evaluated.ipynb') | ||
rel_path_script = nb_basename.replace('.ipynb', '.py') | ||
|
||
# Create python script vesion | ||
unevaluated_text = nb_to_html(nb_abs_path) | ||
script_text = nb_to_python(nb_abs_path) | ||
f = open(dest_path_script, 'wb') | ||
f.write(script_text.encode('utf8')) | ||
f.close() | ||
|
||
skip_exceptions = 'skip_exceptions' in self.options | ||
|
||
try: | ||
evaluated_text = evaluate_notebook(nb_abs_path, dest_path_eval, | ||
skip_exceptions=skip_exceptions) | ||
except: | ||
# bail | ||
return [] | ||
|
||
# Create link to notebook and script files | ||
link_rst = "(" + \ | ||
formatted_link(nb_basename) + "; " + \ | ||
formatted_link(rel_path_eval) + "; " + \ | ||
formatted_link(rel_path_script) + \ | ||
")" | ||
|
||
self.state_machine.insert_input([link_rst], rst_file) | ||
|
||
# create notebook node | ||
attributes = {'format': 'html', 'source': 'nb_path'} | ||
nb_node = notebook_node('', evaluated_text, **attributes) | ||
(nb_node.source, nb_node.line) = \ | ||
self.state_machine.get_source_and_line(self.lineno) | ||
|
||
# add dependency | ||
self.state.document.settings.record_dependencies.add(nb_abs_path) | ||
|
||
# clean up png files left behind by notebooks. | ||
png_files = glob.glob("*.png") | ||
fits_files = glob.glob("*.fits") | ||
h5_files = glob.glob("*.h5") | ||
for file in png_files: | ||
os.remove(file) | ||
|
||
return [nb_node] | ||
|
||
|
||
|
||
class notebook_node(nodes.raw): | ||
pass | ||
|
||
def nb_to_python(nb_path): | ||
"""convert notebook to python script""" | ||
exporter = python.PythonExporter() | ||
output, resources = exporter.from_filename(nb_path) | ||
return output | ||
|
||
def nb_to_html(nb_path): | ||
"""convert notebook to html""" | ||
exporter = html.HTMLExporter(template_file='full') | ||
output, resources = exporter.from_filename(nb_path) | ||
header = output.split('<head>', 1)[1].split('</head>',1)[0] | ||
body = output.split('<body>', 1)[1].split('</body>',1)[0] | ||
|
||
# http://imgur.com/eR9bMRH | ||
header = header.replace('<style', '<style scoped="scoped"') | ||
header = header.replace('body {\n overflow: visible;\n padding: 8px;\n}\n', '') | ||
|
||
# Filter out styles that conflict with the sphinx theme. | ||
filter_strings = [ | ||
'navbar', | ||
'body{', | ||
'alert{', | ||
'uneditable-input{', | ||
'collapse{', | ||
] | ||
filter_strings.extend(['h%s{' % (i+1) for i in range(6)]) | ||
|
||
header_lines = filter( | ||
lambda x: not any([s in x for s in filter_strings]), header.split('\n')) | ||
header = '\n'.join(header_lines) | ||
|
||
# concatenate raw html lines | ||
lines = ['<div class="ipynotebook">'] | ||
lines.append(header) | ||
lines.append(body) | ||
lines.append('</div>') | ||
return '\n'.join(lines) | ||
|
||
def evaluate_notebook(nb_path, dest_path=None, skip_exceptions=False): | ||
# Create evaluated version and save it to the dest path. | ||
# Always use --pylab so figures appear inline | ||
# perhaps this is questionable? | ||
nb_runner = NotebookRunner(nb_in=nb_path, pylab=True) | ||
nb_runner.run_notebook(skip_exceptions=skip_exceptions) | ||
if dest_path is None: | ||
dest_path = 'temp_evaluated.ipynb' | ||
nb_runner.save_notebook(dest_path) | ||
ret = nb_to_html(dest_path) | ||
if dest_path is 'temp_evaluated.ipynb': | ||
os.remove(dest_path) | ||
return ret | ||
|
||
def formatted_link(path): | ||
return "`%s <%s>`__" % (os.path.basename(path), path) | ||
|
||
def visit_notebook_node(self, node): | ||
self.visit_raw(node) | ||
|
||
def depart_notebook_node(self, node): | ||
self.depart_raw(node) | ||
|
||
def setup(app): | ||
setup.app = app | ||
setup.config = app.config | ||
setup.confdir = app.confdir | ||
|
||
app.add_node(notebook_node, | ||
html=(visit_notebook_node, depart_notebook_node)) | ||
|
||
app.add_directive('notebook', NotebookDirective) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import os | ||
import glob | ||
import io | ||
|
||
from sphinx.util.compat import Directive | ||
from docutils.parsers.rst import directives | ||
|
||
from IPython.nbformat import current | ||
|
||
from notebook_sphinxext import notebook_node, visit_notebook_node, \ | ||
depart_notebook_node, evaluate_notebook | ||
|
||
|
||
class NotebookCellDirective(Directive): | ||
"""Insert an evaluated notebook cell into a document | ||
This uses runipy and nbconvert to transform an inline python | ||
script into html suitable for embedding in a Sphinx document. | ||
""" | ||
required_arguments = 0 | ||
optional_arguments = 1 | ||
has_content = True | ||
option_spec = {'skip_exceptions': directives.flag} | ||
|
||
def run(self): | ||
# check if raw html is supported | ||
if not self.state.document.settings.raw_enabled: | ||
raise self.warning('"%s" directive disabled.' % self.name) | ||
|
||
# Construct notebook from cell content | ||
content = "\n".join(self.content) | ||
with open("temp.py", "w") as f: | ||
f.write(content) | ||
|
||
convert_to_ipynb('temp.py', 'temp.ipynb') | ||
|
||
skip_exceptions = 'skip_exceptions' in self.options | ||
|
||
try: | ||
evaluated_text = \ | ||
evaluate_notebook('temp.ipynb', skip_exceptions=skip_exceptions) | ||
except: | ||
# bail | ||
return [] | ||
|
||
# create notebook node | ||
attributes = {'format': 'html', 'source': 'nb_path'} | ||
nb_node = notebook_node('', evaluated_text, **attributes) | ||
(nb_node.source, nb_node.line) = \ | ||
self.state_machine.get_source_and_line(self.lineno) | ||
|
||
# clean up | ||
files = glob.glob("*.png") + ['temp.py', 'temp.ipynb'] | ||
for file in files: | ||
os.remove(file) | ||
|
||
return [nb_node] | ||
|
||
|
||
def setup(app): | ||
setup.app = app | ||
setup.config = app.config | ||
setup.confdir = app.confdir | ||
|
||
app.add_node(notebook_node, | ||
html=(visit_notebook_node, depart_notebook_node)) | ||
|
||
app.add_directive('notebook-cell', NotebookCellDirective) | ||
|
||
|
||
def convert_to_ipynb(py_file, ipynb_file): | ||
with io.open(py_file, 'r', encoding='utf-8') as f: | ||
notebook = current.reads(f.read(), format='py') | ||
with io.open(ipynb_file, 'w', encoding='utf-8') as f: | ||
current.write(notebook, f, format='ipynb') |