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

testing single doc mode #114

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion jupyter_book/book_template/_config.yml
Expand Up @@ -97,7 +97,7 @@ images_url : "/assets/images" # Path to static image files
css_url : "/assets/css" # Path to static CSS files
js_url : "/assets/js" # Path to JS files
custom_static_url : "/assets/custom" # Path to user's custom CSS/JS files

page_nav : true # Whether to show page navigation on each page

#######################################################################################
# Jekyll build settings (only modify if you know what you're doing)
Expand Down
2 changes: 1 addition & 1 deletion jupyter_book/book_template/_layouts/default.html
Expand Up @@ -20,7 +20,7 @@
{% include buttons.html %}
<div class="c-textbook__content">
{{ content }}
{% include page-nav.html %}
{%- if site.page_nav %}{% include page-nav.html %}{% endif %}
</div>
</div>
</div>
Expand Down
5 changes: 4 additions & 1 deletion jupyter_book/book_template/assets/js/scripts.js
Expand Up @@ -55,7 +55,10 @@ const autoCloseSidebarBreakpoint = 740

// Set up event listener for sidebar toggle button
const sidebarButtonHandler = () => {
getToggler().addEventListener('click', toggleSidebar)
var toggler = getToggler();
if (toggler) {
toggler.addEventListener('click', toggleSidebar)
}

/**
* Auto-close sidebar on smaller screens after page load.
Expand Down

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions jupyter_book/book_template/content/guide/05_advanced.md
Expand Up @@ -76,3 +76,17 @@ courses / workshops of less than 50-60 students.
Once you have your JupyterHub set up, you can use the [nbgitpuller](https://github.com/data-8/nbgitpuller)
package to send links to course material to your students, or use the interact links that Textbooks for Jupyter
automatically inserts into your course material.

## Generate reports from a single notebook

**Warning**: Report generation is experimental and subject to change!

Sometimes you don't need an entire book, but instead would like a single document
generated from a Jupyter Notebook. For example, if you're creating a report from
one notebook that shows off your analysis.

To generate a report for a notebook, use the `--notebook` flag to point to an `.ipynb` file, like so:

```
jupyter-book create myreport --notebook path/to/myfile.ipynb
```
16 changes: 7 additions & 9 deletions jupyter_book/build.py
@@ -1,4 +1,4 @@
from subprocess import check_call
from subprocess import run
import os
import os.path as op
import sys
Expand Down Expand Up @@ -128,7 +128,7 @@ def build_book(path_book, path_toc_yaml=None, config_file=None,
# Load the yaml for this site
with open(config_file, 'r') as ff:
site_yaml = yaml.load(ff.read())
CONTENT_FOLDER_NAME = site_yaml.get('content_folder_name').strip('/')
CONTENT_FOLDER_NAME = site_yaml.get('content_folder_name', 'content').strip('/')
PATH_CONTENT_FOLDER = op.join(path_book, CONTENT_FOLDER_NAME)

# Load the textbook yaml for this site
Expand Down Expand Up @@ -244,19 +244,17 @@ def build_book(path_book, path_toc_yaml=None, config_file=None,
build_call = '--FilesWriter.build_directory={}'.format(
path_new_folder)
# Copy notebook output images to the build directory using the base folder name
path_after_build_folder = path_new_folder.split(
os.sep + BUILD_FOLDER_NAME + os.sep)[-1]
nb_output_folder = op.join(
PATH_IMAGES_FOLDER, path_after_build_folder)
images_call = '--NbConvertApp.output_files_dir={}'.format(
nb_output_folder)
path_after_build_folder = path_new_folder.split(os.sep + BUILD_FOLDER_NAME)[-1].lstrip('/')
nb_output_folder = op.join('images', path_after_build_folder)
images_call = '--NbConvertApp.output_files_dir={}'.format(nb_output_folder)
call = ['jupyter', 'nbconvert', '--log-level="CRITICAL"',
'--to', 'markdown', '--template', path_template,
images_call, build_call, tmp_notebook]

if execute is True:
call.insert(-1, '--execute')

check_call(call)
run(call, check=True)
os.remove(tmp_notebook)

elif path_url_page.endswith('.md'):
Expand Down
1 change: 1 addition & 0 deletions jupyter_book/commands/__init__.py
Expand Up @@ -3,3 +3,4 @@
from .create import create
from .upgrade import upgrade
from .run import run
from .report import report
1 change: 0 additions & 1 deletion jupyter_book/commands/create.py
Expand Up @@ -5,7 +5,6 @@

from jupyter_book.create import new_book


def create():
parser = argparse.ArgumentParser(description="Create a new Jupyter Book")
parser.add_argument(
Expand Down
23 changes: 23 additions & 0 deletions jupyter_book/commands/report.py
@@ -0,0 +1,23 @@
import sys
import os.path as op

import argparse

from jupyter_book.report import new_report

def report():
parser = argparse.ArgumentParser(description="Generate a report from a jupyter notebook")
parser.add_argument('path_notebook', help="The path to a Jupyter Notebook")
parser.add_argument('--path-output', help="The path to the output report")
parser.add_argument('--config', default=None, help="The path to a configuration file for this report")
parser.add_argument('--css',default=None, help="A path to custom CSS to include with the report")
parser.add_argument('--js', default=None, help="A path to custom JavaScript to include with the report")
parser.add_argument("--overwrite", default=False, action="store_true", help="Whether to overwrite a pre-existing report if it exists")

# Parse inputs
args = parser.parse_args(sys.argv[2:])
path_notebook = args.path_notebook
path_output_report = op.basename(path_notebook).replace('.ipynb', '') if args.path_output is None else args.path_output


new_report(path_notebook, path_output_report, args.config, args.css, args.js, args.overwrite)
3 changes: 1 addition & 2 deletions jupyter_book/create.py
Expand Up @@ -64,7 +64,7 @@ def update_config(path_to_config, new_config):


def new_book(path_out, content_folder, toc,
license, custom_css=None, custom_js=None, config=None,
license, notebook=None, custom_css=None, custom_js=None, config=None,
extra_files=None, demo=False, verbose=True,
overwrite=None):
"""Create a new Jupyter Book.
Expand Down Expand Up @@ -257,7 +257,6 @@ def upgrade_book(path_book):
# Now create a new book from the old one
try:
print("Creating new book from your original one...")

new_book(path_book_new, toc=op.join(path_book, '_data', 'toc.yml'),
content_folder=op.join(path_book, 'content'),
license=op.join(path_book, 'content', 'LICENSE.md'),
Expand Down
6 changes: 4 additions & 2 deletions jupyter_book/main.py
@@ -1,7 +1,7 @@
import sys
import argparse

from .commands import build, create, upgrade
from .commands import build, create, upgrade, report
from .run import run_book
from .toc import build_toc

Expand All @@ -12,7 +12,9 @@
'build': build,
'upgrade': upgrade,
'run': run_book,
'toc': build_toc}
'toc': build_toc,
'report': report}

parser = argparse.ArgumentParser(description=DESCRIPTION)
parser.add_argument("command", help="The command you'd like to run. Allowed commands: {}".format(
list(commands.keys())))
Expand Down
129 changes: 129 additions & 0 deletions jupyter_book/report.py
@@ -0,0 +1,129 @@
import os.path as op
import os
import sys
import shutil as sh
from ruamel.yaml import YAML
import nbformat as nbf
from subprocess import run, PIPE
from .utils import print_message_box
from .create import new_book
from .build import build_book
from distutils.dir_util import copy_tree
from glob import glob
import sass

def new_report(path_notebook, path_output, config, css, js, overwrite=False):
"""Create a new report from a single notebook."""

# Check if we've specified a single notebook (and in this case will use the report script)
if not op.exists(path_notebook):
raise ValueError(f"Could not find the notebook specified at {path_notebook}")
if not path_notebook.endswith('.ipynb'):
raise ValueError(f"Specified path_notebook file does not end in .ipynb, found {op.splitext(path_notebook)[-1]}")

name_notebook = op.basename(path_notebook)

# Remove an old report
if op.isdir(path_output):
if overwrite is True:
sh.rmtree(path_output)
else:
raise ValueError(f"Found a pre-existing report at {path_output}. Please delete it or use `--overwrite`.")

# Copy the notebook to a temporary folder
print("Creating temporary folder...")
path_temp = op.join(path_output, 'TMP')
raw_folder = op.join(path_temp, 'raw')

# Define paths for content we'll use to build the report
path_tmp_config = op.join(raw_folder, '_config.yml') # Config for the report
path_tmp_content = op.join(raw_folder, 'content') # Content folder for the report book
path_tmp_notebook = op.join(path_tmp_content, op.basename(path_notebook)) # Where the report will be generated
path_tmp_book = op.join(path_temp, 'book') # Where the jupyter book for the report is generated
path_html = op.join(path_temp, 'html') # Where the HTML will be built

# Make content directory and copy our notebook
os.makedirs(path_tmp_content, exist_ok=True)
sh.copy2(path_notebook, path_tmp_notebook)

##### Modify the notebook content ###############################################

## Hide code cells for the report
ntbk = nbf.read(path_tmp_notebook, nbf.NO_CONVERT)
for cell in ntbk['cells']:
if cell['cell_type'] == 'code':
tags = cell['metadata'].get('tags', [])
if not 'hidecode' in tags:
tags.append('hidecode')
cell['metadata']['tags'] = tags
nbf.write(ntbk, path_tmp_notebook)


##### Create book for the report ###############################################

# Add some config for a report


# ##############################################################################
# TODO: figure out the updating stuff here




yaml = YAML()
default_config = {
"show_sidebar": False,
"baseurl": "",
"url": "",
"page_nav": False,
"use_binder_button": False,
"use_thebelab_button": False,
"use_show_widgets_button": False,
"use_download_button": False
}
if config is not None:
if not op.exists(config):
raise ValueError("Configuration file not found: {}".format(config))
with open(config, 'r') as ff:
custom_config = yaml.load(ff)
default_config.update(custom_config)
with open(path_tmp_config, 'w') as ff:
yaml.dump(config, ff)

# Create a new book for this report
print("Creating new book...")
new_book(path_tmp_book, path_tmp_content, None, None, config=path_tmp_config, overwrite=overwrite)

# Build the markdown for the book
print("Building book markdown...")
path_toc = op.join(path_tmp_book, '_data', 'toc.yml')
path_template = op.join(path_tmp_book, 'scripts', 'templates', 'jekyllmd.tpl')
build_book(path_tmp_book, path_toc_yaml=path_toc, path_template=path_template, config_file=path_tmp_config)

# Build the HTML in the temp folder
print("Building report HTML...")
path_html_rel_build_folder = op.relpath(path_html, path_tmp_book)
cmd = f'bundle exec jekyll build -d {path_html_rel_build_folder}'
run(cmd.split(), check=True, cwd=path_tmp_book)

##### Clean up HTML ###########################################################

# Move the content HTML file to the index.html spot
path_html_index = op.join(path_html, "index.html")
os.remove(path_html_index)
sh.move(op.join(path_html, name_notebook.replace('.ipynb', '.html')), path_html_index)

# Remove paths relative to root of folder
with open(path_html_index, 'r') as ff:
text = ff.read()
text = text.replace('href="/', 'href="')
text = text.replace('src="/', 'src="')
with open(path_html_index, 'w') as ff:
ff.write(text)

# Copy into the output directory, overwriting it
copy_tree(path_html, path_output)
sh.rmtree(path_temp)
path_output_index = op.join(path_output, 'index.html')
msg = f"Done generating report!\n\nYour report is here: {path_output_index}"
print_message_box(msg)
397 changes: 397 additions & 0 deletions jupyter_book/tests/site/content/tests/report.ipynb

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions jupyter_book/tests/test_create.py
Expand Up @@ -137,6 +137,27 @@ def test_upgrade(tmpdir):
text = ff.read()
assert "RANDOMTEXT" not in text


def test_report(tmpdir):
path_ntbk = op.join(this_folder, 'site', 'content', 'tests', 'report.ipynb')
path_out = op.join(tmpdir.dirpath(), 'tmp_report')

# Custom CSS and JS code
# TODO: COMMENTED OUT FOR NOW
# path_js = op.join(path_test_book, "my_js.js")
# path_css = op.join(path_test_book, "my_css.css")

# Run the create command
new_name = "testreport"
cmd = ["jupyter-book", "create", new_name,
"--notebook", path_ntbk,
"--out-folder", path_out]
# "--custom-js", path_js , "--custom-css", path_css,
run(cmd, check=True)

# Make sure the proper report files are there
assert op.exists(op.join(path_out, new_name, 'index.html'))

########################################################################################################
# Building the book after the book is created
########################################################################################################
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Expand Up @@ -6,3 +6,4 @@ pyyaml
nbformat
nbclean
setuptools
libsass