Repository: executablebooks/jupyter-book
File: jupyter_book/utils.py (Python)

Code before:

    # Make sure it ends in "HTML"
    if not url.endswith('.html'):
        url = op.splitext(url)[0] + '.html'
    return url


def _clean_markdown_cells(ntbk):
    """Clean up cell text of an nbformat NotebookNode."""
    # Remove '#' from the end of markdown headers
    for cell in ntbk.cells:
        if cell.cell_type == "markdown":
            cell_lines = cell.source.split('\n')
            for ii, line in enumerate(cell_lines):
                if line.startswith('#'):
                    cell_lines[ii] = line.rstrip('#').rstrip()
            cell.source = '\n'.join(cell_lines)
    return ntbk


def read_notebook(path_notebook):
    """Read in a notebook using either nbformat or jupytext."""
    suff = op.splitext(path_notebook)[-1]
    if suff == ".ipynb":
        ntbk = nbf.read(path_notebook, nbf.NO_CONVERT)
    elif suff == ".md":
        ntbk = jpt.read(path_notebook)
    return ntbk


Review comment (suggestion):
If you wanted, you could use `jpt.read(path_notebook)` even when the notebook has an `.ipynb` extension.

Code after:
"""Utility functions for Jupyter Book."""

import string
import argparse
import os
import os.path as op

##############################################################################
# CLI utilities


def print_color(msg, style):
    endc = '\033[0m'
    bcolors = dict(blue='\033[94m',
                   green='\033[92m',
                   orange='\033[93m',
                   red='\033[91m',
                   bold='\033[1m',
                   underline='\033[4m')
    print(bcolors[style] + msg + endc)


def print_message_box(msg):
    border = '================================================================================'
    print_color('\n\n' + border + '\n\n', 'green')
    print(msg)
    print_color('\n\n' + border + '\n\n', 'green')


def _error(msg):
    msg = '\n\n\033[91m==========\033[0m\n{}\n\033[91m==========\033[0m\n'.format(
        msg)
    return ValueError(msg)


def str2bool(msg):
    if msg.lower() in ('yes', 'true', 't', 'y', '1'):
        return True
    elif msg.lower() in ('no', 'false', 'f', 'n', '0'):
        return False
    else:
        raise argparse.ArgumentTypeError(
            'Boolean value expected. Got: {}'.format(msg))

##############################################################################
# Book conversion formatting


ALLOWED_CHARACTERS = string.ascii_letters + '-_/.' + string.digits


def _split_yaml(lines):
    yaml0 = None
    for ii, iline in enumerate(lines):
        iline = iline.strip()
        if yaml0 is None:
            if iline == '---':
                yaml0 = ii
            elif iline:
                break
        elif iline == '---':
            return lines[yaml0 + 1:ii], lines[ii + 1:]
    return [], lines


def _check_url_page(url_page, content_folder_name):
    """Check that the page URL matches certain conditions."""
    if not all(ii in ALLOWED_CHARACTERS for ii in url_page):
        raise ValueError(
            "Found unsupported character in filename: {}".format(url_page))
    if '.' in os.path.splitext(url_page)[-1]:
        raise _error("A toc.yml entry links to a file directly. You should strip the file suffix.\n"
                     "Please change {} to {}".format(url_page, os.path.splitext(url_page)[0]))
    if any(url_page.startswith(ii) for ii in [content_folder_name, os.sep + content_folder_name]):
        raise ValueError("It looks like you have a page URL that starts with your content folder's name."
                         "page URLs should be *relative* to the content folder. Here is the page URL: {}".format(url_page))


def _prepare_toc(toc):
    """Prepare the TOC for processing."""
    # Drop toc items w/o links
    toc = [ii for ii in toc if ii.get('url', None) is not None]
    # Un-nest the TOC so it's a flat list
    new_toc = []
    for ii in toc:
        sections = ii.pop('sections', None)
        new_toc.append(ii)
        if sections is None:
            continue
        for jj in sections:
            subsections = jj.pop('subsections', None)
            new_toc.append(jj)
            if subsections is None:
                continue
            for kk in subsections:
                new_toc.append(kk)
    return new_toc


def _prepare_url(url):
    """Prep the formatting for a url."""
    # Strip suffixes and prefixes of the URL
    if not url.startswith('/'):
        url = '/' + url

    # Standardize the quotes character
    url = url.replace('"', "'")

    # Make sure it ends in "HTML"
    if not url.endswith('.html'):
        url = op.splitext(url)[0] + '.html'
    return url


def _clean_markdown_cells(ntbk):
    """Clean up cell text of an nbformat NotebookNode."""
    # Remove '#' from the end of markdown headers
    for cell in ntbk.cells:
        if cell.cell_type == "markdown":
            cell_lines = cell.source.split('\n')
            for ii, line in enumerate(cell_lines):
                if line.startswith('#'):
                    cell_lines[ii] = line.rstrip('#').rstrip()
            cell.source = '\n'.join(cell_lines)
    return ntbk


def _file_newer_than(path1, path2):
    """Check whether file at path1 is newer than path2."""
    return os.stat(path1).st_mtime > os.stat(path2).st_mtime