Exemple #1
0
def cwiki_figure(m):
    filename = m.group('filename')
    link = filename if filename.startswith('http') else None
    if not link and not os.path.isfile(filename):
        raise IOError('no figure file %s' % filename)

    basename = os.path.basename(filename)
    stem, ext = os.path.splitext(basename)
    root, ext = os.path.splitext(filename)

    if link is None:
        if not ext in '.png .gif .jpg .jpeg'.split():
            # try to convert image file to PNG, using
            # convert from ImageMagick:
            cmd = 'convert %s png:%s' % (filename, root + '.png')
            try:
                output = subprocess.check_output(cmd,
                                                 shell=True,
                                                 stderr=subprocess.STDOUT)
            except subprocess.CalledProcessError as e:
                errwarn('\n**** Warning: could not run ' + cmd)
                errwarn('Convert %s to PNG format manually' % filename)
                _abort()
            filename = root + '.png'
    caption = m.group('caption')
    # keep label if it's there:
    caption = re.sub(r'label\{(.+?)\}', '(\g<1>)', caption)

    result = r"""{{%s|%s}}""" % (filename, caption)
    return result
Exemple #2
0
def cwiki_figure(m):
    filename = m.group('filename')
    link = filename if filename.startswith('http') else None
    if not link and not os.path.isfile(filename):
        raise IOError('no figure file %s' % filename)

    basename = os.path.basename(filename)
    stem, ext = os.path.splitext(basename)
    root, ext = os.path.splitext(filename)

    if link is None:
        if not ext in '.png .gif .jpg .jpeg'.split():
            # try to convert image file to PNG, using
            # convert from ImageMagick:
            cmd = 'convert %s png:%s' % (filename, root + '.png')
            failure, output = commands.getstatusoutput(cmd)
            if failure:
                print '\n**** Warning: could not run', cmd
                print 'Convert %s to PNG format manually' % filename
                _abort()
            filename = root + '.png'
    caption = m.group('caption')
    # keep label if it's there:
    caption = re.sub(r'label\{(.+?)\}', '(\g<1>)', caption)

    result = r"""{{%s|%s}}""" % (filename, caption)
    return result
Exemple #3
0
def cwiki_figure(m):
    filename = m.group('filename')
    link = filename if filename.startswith('http') else None
    if not link and not os.path.isfile(filename):
        raise IOError('no figure file %s' % filename)

    basename  = os.path.basename(filename)
    stem, ext = os.path.splitext(basename)
    root, ext = os.path.splitext(filename)

    if link is None:
        if not ext in '.png .gif .jpg .jpeg'.split():
            # try to convert image file to PNG, using
            # convert from ImageMagick:
            cmd = 'convert %s png:%s' % (filename, root+'.png')
            try:
                output = subprocess.check_output(cmd, shell=True,
                                                 stderr=subprocess.STDOUT)
            except subprocess.CalledProcessError as e:
                errwarn('\n**** Warning: could not run ' + cmd)
                errwarn('Convert %s to PNG format manually' % filename)
                _abort()
            filename = root + '.png'
    caption = m.group('caption')
    # keep label if it's there:
    caption = re.sub(r'label\{(.+?)\}', '(\g<1>)', caption)

    result = r"""{{%s|%s}}""" % (filename, caption)
    return result
Exemple #4
0
def begin_end_consistency_checks(filestr, envirs):
    """Perform consistency checks: no of !bc must equal no of !ec, etc."""
    for envir in envirs:
        begin = '!b' + envir
        end = '!e' + envir

        nb = len(re.findall(r'^%s' % begin, filestr, flags=re.MULTILINE))
        ne = len(re.findall(r'^%s' % end, filestr, flags=re.MULTILINE))

        lines = []
        if nb != ne:
            print 'ERROR: %d %s do not match %d %s directives' % \
                  (nb, begin, ne, end)
            if not lines:
                lines = filestr.splitlines()
            begin_ends = []
            for i, line in enumerate(lines):
                if line.startswith(begin):
                    begin_ends.append((begin, i))
                if line.startswith(end):
                    begin_ends.append((end, i))
            for k in range(1, len(begin_ends)):
                pattern, i = begin_ends[k]
                if pattern == begin_ends[k-1][0]:
                    print '\n\nTwo', pattern, 'after each other!\n'
                    for j in range(begin_ends[k-1][1], begin_ends[k][1]+1):
                        print lines[j]
                    _abort()
            if begin_ends[-1][0].startswith('!b'):
                print 'Missing %s after final %s' % \
                      (begin_ends[-1][0].replace('!b', '!e'),
                       begin_ends[-1][0])
                _abort()
Exemple #5
0
def begin_end_consistency_checks(filestr, envirs):
    """Perform consistency checks: no of !bc must equal no of !ec, etc."""
    for envir in envirs:
        begin = '!b' + envir
        end = '!e' + envir

        nb = len(re.findall(r'^%s' % begin, filestr, flags=re.MULTILINE))
        ne = len(re.findall(r'^%s' % end, filestr, flags=re.MULTILINE))

        lines = []
        if nb != ne:
            print 'ERROR: %d %s do not match %d %s directives' % \
                  (nb, begin, ne, end)
            if not lines:
                lines = filestr.splitlines()
            begin_ends = []
            for i, line in enumerate(lines):
                if line.startswith(begin):
                    begin_ends.append((begin, i))
                if line.startswith(end):
                    begin_ends.append((end, i))
            for k in range(1, len(begin_ends)):
                pattern, i = begin_ends[k]
                if pattern == begin_ends[k-1][0]:
                    print '\n\nTwo', pattern, 'after each other!\n'
                    for j in range(begin_ends[k-1][1], begin_ends[k][1]+1):
                        print lines[j]
                    _abort()
            if begin_ends[-1][0].startswith('!b'):
                print 'Missing %s after final %s' % \
                      (begin_ends[-1][0].replace('!b', '!e'),
                       begin_ends[-1][0])
                _abort()
Exemple #6
0
def cwiki_figure(m):
    filename = m.group('filename')
    link = filename if filename.startswith('http') else None
    if not link and not os.path.isfile(filename):
        raise IOError('no figure file %s' % filename)

    basename  = os.path.basename(filename)
    stem, ext = os.path.splitext(basename)
    root, ext = os.path.splitext(filename)

    if link is None:
        if not ext in '.png .gif .jpg .jpeg'.split():
            # try to convert image file to PNG, using
            # convert from ImageMagick:
            cmd = 'convert %s png:%s' % (filename, root+'.png')
            failure, output = commands.getstatusoutput(cmd)
            if failure:
                print '\n**** Warning: could not run', cmd
                print 'Convert %s to PNG format manually' % filename
                _abort()
            filename = root + '.png'
    caption = m.group('caption')
    # keep label if it's there:
    caption = re.sub(r'label\{(.+?)\}', '(\g<1>)', caption)

    result = r"""{{%s|%s}}""" % (filename, caption)
    return result
Exemple #7
0
 def subst_footnote(m):
     name = m.group('name').strip()
     if name in name2index:
         i = name2index[m.group('name')]
     else:
         errwarn('*** error: found footnote with name "%s", but this one is not defined' % name)
         _abort()
     xml = r'<footnote id="%s">%s<footnote>' % (i, name)
     return xml
Exemple #8
0
 def subst_footnote(m):
     name = m.group('name').strip()
     if name in name2index:
         i = name2index[m.group('name')]
     else:
         print '*** error: found footnote with name "%s", but this one is not defined' % name
         _abort()
     xml = r'<footnote id="%s">%s<footnote>' % (i, name)
     return xml
Exemple #9
0
def safe_join(lines, delimiter):
    try:
        filestr = delimiter.join(lines) + '\n' # will fail if ord(char) > 127
        return filestr
    except UnicodeDecodeError, e:
        if "'ascii' codec can't decode":
            print '*** error: non-ascii character - rerun with --encoding=utf-8'
            _abort()
        else:
            print e
            _abort()
Exemple #10
0
def safe_join(lines, delimiter):
    try:
        filestr = delimiter.join(lines) + '\n' # will fail if ord(char) > 127
        return filestr
    except UnicodeDecodeError, e:
        if "'ascii' codec can't decode":
            print '*** error: non-ascii character - rerun with --encoding=utf-8'
            _abort()
        else:
            print e
            _abort()
Exemple #11
0
def gwiki_figure(m):
    filename = m.group("filename")
    link = filename if filename.startswith("http") else None
    if not link and not os.path.isfile(filename):
        raise IOError("no figure file %s" % filename)

    basename = os.path.basename(filename)
    stem, ext = os.path.splitext(basename)
    root, ext = os.path.splitext(filename)

    if link is None:
        if not ext in ".png .gif .jpg .jpeg".split():
            # try to convert image file to PNG, using
            # convert from ImageMagick:
            cmd = "convert %s png:%s" % (filename, root + ".png")
            failure, output = commands.getstatusoutput(cmd)
            if failure:
                print "\n**** Warning: could not run", cmd
                print "Convert %s to PNG format manually" % filename
                _abort()
            filename = root + ".png"
    caption = m.group("caption")
    # keep label if it's there:
    caption = re.sub(r"label\{(.+?)\}", "(\g<1>)", caption)

    print """
NOTE: Place %s at some place on the web and edit the
      .gwiki page, either manually (seach for 'Figure: ')
      or use the doconce script:
      doconce gwiki_figsubst.py mydoc.gwiki URL
""" % filename

    result = r"""

---------------------------------------------------------------

Figure: %s

(the URL of the image file %s must be inserted here)

<wiki:comment>
Put the figure file %s on the web (e.g., as part of the
googlecode repository) and substitute the line above with the URL.
</wiki:comment>
---------------------------------------------------------------

""" % (
        caption,
        filename,
        filename,
    )
    return result
Exemple #12
0
def gwiki_figure(m):
    filename = m.group('filename')
    link = filename if filename.startswith('http') else None
    if not link and not os.path.isfile(filename):
        raise IOError('no figure file %s' % filename)

    basename = os.path.basename(filename)
    stem, ext = os.path.splitext(basename)
    root, ext = os.path.splitext(filename)

    if link is None:
        if not ext in '.png .gif .jpg .jpeg'.split():
            # try to convert image file to PNG, using
            # convert from ImageMagick:
            cmd = 'convert %s png:%s' % (filename, root + '.png')
            failure, output = commands.getstatusoutput(cmd)
            if failure:
                errwarn('\n**** Warning: could not run ' + cmd)
                errwarn('Convert %s to PNG format manually' % filename)
                _abort()
            filename = root + '.png'
    caption = m.group('caption')
    # keep label if it's there:
    caption = re.sub(r'label\{(.+?)\}', '(\g<1>)', caption)

    errwarn("""
NOTE: Place %s at some place on the web and edit the
      .gwiki page, either manually (seach for 'Figure: ')
      or use the doconce script:
      doconce gwiki_figsubst.py mydoc.gwiki URL
""" % filename)

    result = r"""

---------------------------------------------------------------

Figure: %s

(the URL of the image file %s must be inserted here)

<wiki:comment>
Put the figure file %s on the web (e.g., as part of the
googlecode repository) and substitute the line above with the URL.
</wiki:comment>
---------------------------------------------------------------

""" % (caption, filename, filename)
    return result
Exemple #13
0
    def YouTubeVideo(filename):
        # Use YouTubeVideo object
        if 'watch?v=' in filename:
            name = filename.split('watch?v=')[1]
        elif 'youtu.be/' in filename:
            name = filename.split('youtu.be/')[1]
        else:
            print '*** error: youtube movie name "%s" could not be interpreted' % filename
            _abort()

        text = ''
        global movie_encountered
        if not movie_encountered:
            text += 'from IPython.display import YouTubeVideo\n'
            movie_encountered = True
        text += 'YouTubeVideo("%s")\n' % name
        return text
Exemple #14
0
    def YouTubeVideo(filename):
        # Use YouTubeVideo object
        if 'watch?v=' in filename:
            name = filename.split('watch?v=')[1]
        elif 'youtu.be/' in filename:
            name = filename.split('youtu.be/')[1]
        else:
            errwarn('*** error: youtube movie name "%s" could not be interpreted' % filename)
            _abort()

        text = ''
        global movie_encountered
        if not movie_encountered:
            text += 'from IPython.display import YouTubeVideo\n'
            movie_encountered = True
        text += 'YouTubeVideo("%s")\n' % name
        return text
Exemple #15
0
def sphinx_inline_comment(m):
    # Explicit HTML typesetting does not work, we just use bold
    name = m.group('name').strip()
    comment = m.group('comment').strip()

    global edit_markup_warning
    if (not edit_markup_warning) and \
           (name[:3] in ('add', 'del', 'edi') or '->' in comment):
        errwarn('*** warning: sphinx/rst is a suboptimal format for')
        errwarn('    typesetting edit markup such as')
        errwarn('    ' + m.group())
        errwarn('    Use HTML or LaTeX output instead, implement the')
        errwarn('    edits (doconce apply_edit_comments) and then use sphinx.')
        edit_markup_warning = True

    chars = {',': 'comma', ';': 'semicolon', '.': 'period'}
    if name[:4] == 'del ':
        for char in chars:
            if comment == char:
                return r' (**edit %s**: delete %s)' % (name[4:], chars[char])
        return r'(**edit %s**: **delete** %s)' % (name[4:], comment)
    elif name[:4] == 'add ':
        for char in chars:
            if comment == char:
                return r'%s (**edit %s: add %s**)' % (comment, name[4:],
                                                      chars[char])
        return r' (**edit %s: add**) %s (**end add**)' % (name[4:], comment)
    else:
        # Ordinary name
        comment = ' '.join(comment.splitlines())  # '\s->\s' -> ' -> '
        if ' -> ' in comment:
            # Replacement
            if comment.count(' -> ') != 1:
                errwarn('*** wrong syntax in inline comment:')
                errwarn(comment)
                errwarn('(more than two ->)')
                _abort()
            orig, new = comment.split(' -> ')
            return r'(**%s: remove** %s) (**insert:**)%s (**end insert**)' % (
                name, orig, new)
        else:
            # Ordinary comment
            return r'[**%s**: %s]' % (name, comment)
Exemple #16
0
def align2equations(filestr, format):
    """Turn align environments into separate equation environments."""
    if not '{align}' in filestr:
        return filestr

    # sphinx: just replace align, pandoc/ipynb: replace align and align*
    # technique: add } if sphinx
    postfixes = ['}'] if format == 'sphinx' else ['}', '*}']

    lines = filestr.splitlines()
    inside_align = False
    inside_code = False
    for postfix in postfixes:
        for i in range(len(lines)):
            if lines[i].startswith('!bc'):
                inside_code = True
            if lines[i].startswith('!ec'):
                inside_code = False
            if inside_code:
                continue

            if r'\begin{align%s' % postfix in lines[i]:
                inside_align = True
                lines[i] = lines[i].replace(
                r'\begin{align%s' % postfix, r'\begin{equation%s' % postfix)
            if inside_align and '\\\\' in lines[i]:
                lines[i] = lines[i].replace(
                '\\\\', '\n' + r'\end{equation%s' % postfix + '\n!et\n\n!bt\n' + r'\begin{equation%s ' % postfix)
            if inside_align and ('begin{array}' in lines[i] or
                                 'begin{bmatrix}' in lines[i]):
                print '*** error: with %s output, align environments' % format
                print '    cannot have arrays/matrices with & and \\\\'
                print '    rewrite with single equations!'
                print '\n'.join(lines[i-4:i+5]).replace('{equation', '{align')
                _abort()
            if inside_align and '&' in lines[i]:
                lines[i] = lines[i].replace('&', '')
            if r'\end{align%s' % postfix in lines[i]:
                inside_align = False
                lines[i] = lines[i].replace(
                r'\end{align%s' % postfix, r'\end{equation%s' % postfix)
    filestr = '\n'.join(lines)
    return filestr
Exemple #17
0
def sphinx_inline_comment(m):
    # Explicit HTML typesetting does not work, we just use bold
    name = m.group('name').strip()
    comment = m.group('comment').strip()

    global edit_markup_warning
    if (not edit_markup_warning) and \
           (name[:3] in ('add', 'del', 'edi') or '->' in comment):
        print '*** warning: sphinx/rst is a suboptimal format for'
        print '    typesetting edit markup such as'
        print '   ', m.group()
        print '    Use HTML or LaTeX output instead, implement the'
        print '    edits (doconce apply_edit_comments) and then use sphinx.'
        edit_markup_warning = True

    chars = {',': 'comma', ';': 'semicolon', '.': 'period'}
    if name[:4] == 'del ':
        for char in chars:
            if comment == char:
                return r' (**edit %s**: delete %s)' % (name[4:], chars[char])
        return r'(**edit %s**: **delete** %s)' % (name[4:], comment)
    elif name[:4] == 'add ':
        for char in chars:
            if comment == char:
                return r'%s (**edit %s: add %s**)' % (comment, name[4:], chars[char])
        return r' (**edit %s: add**) %s (**end add**)' % (name[4:], comment)
    else:
        # Ordinary name
        comment = ' '.join(comment.splitlines()) # '\s->\s' -> ' -> '
        if ' -> ' in comment:
            # Replacement
            if comment.count(' -> ') != 1:
                print '*** wrong syntax in inline comment:'
                print comment
                print '(more than two ->)'
                _abort()
            orig, new = comment.split(' -> ')
            return r'(**%s: remove** %s) (**insert:**)%s (**end insert**)' % (name, orig, new)
        else:
            # Ordinary comment
            return r'[**%s**: %s]' % (name, comment)
Exemple #18
0
def sphinx_inline_comment(m):
    # Explicit HTML typesetting does not work, we just use bold
    name = m.group("name").strip()
    comment = m.group("comment").strip()

    global edit_markup_warning
    if (not edit_markup_warning) and (name[:3] in ("add", "del", "edi") or "->" in comment):
        errwarn("*** warning: sphinx/rst is a suboptimal format for")
        errwarn("    typesetting edit markup such as")
        errwarn("    " + m.group())
        errwarn("    Use HTML or LaTeX output instead, implement the")
        errwarn("    edits (doconce apply_edit_comments) and then use sphinx.")
        edit_markup_warning = True

    chars = {",": "comma", ";": "semicolon", ".": "period"}
    if name[:4] == "del ":
        for char in chars:
            if comment == char:
                return r" (**edit %s**: delete %s)" % (name[4:], chars[char])
        return r"(**edit %s**: **delete** %s)" % (name[4:], comment)
    elif name[:4] == "add ":
        for char in chars:
            if comment == char:
                return r"%s (**edit %s: add %s**)" % (comment, name[4:], chars[char])
        return r" (**edit %s: add**) %s (**end add**)" % (name[4:], comment)
    else:
        # Ordinary name
        comment = " ".join(comment.splitlines())  # '\s->\s' -> ' -> '
        if " -> " in comment:
            # Replacement
            if comment.count(" -> ") != 1:
                errwarn("*** wrong syntax in inline comment:")
                errwarn(comment)
                errwarn("(more than two ->)")
                _abort()
            orig, new = comment.split(" -> ")
            return r"(**%s: remove** %s) (**insert:**)%s (**end insert**)" % (name, orig, new)
        else:
            # Ordinary comment
            return r"[**%s**: %s]" % (name, comment)
Exemple #19
0
def insert_code_and_tex(filestr, code_blocks, tex_blocks, format):
    # Consistency check: find no of distinct code and math blocks
    # (can be duplicates when solutions are copied at the end)
    import sets
    pattern = r'^\d+ ' + _CODE_BLOCK
    n = len(sets.Set(re.findall(pattern, filestr, flags=re.MULTILINE)))
    if len(code_blocks) != n:
        print '*** error: found %d code block markers for %d initial code blocks' % (n, len(code_blocks))
        print """    Possible causes:
           - mismatch of !bt and !et within one file, such that a !bt
             swallows code
           - mismatch of !bt and !et across files in multi-file documents
           - !bc and !ec inside code blocks - replace by |bc and |ec
    (run doconce on each file to locate the problem, then on
     smaller and smaller parts of each file)"""
        _abort()
    pattern = r'^\d+ ' + _MATH_BLOCK
    n = len(sets.Set(re.findall(pattern, filestr, flags=re.MULTILINE)))
    if len(tex_blocks) != n:
        print '*** error: found %d tex block markers for %d initial tex blocks\nAbort!' % (n, len(tex_blocks))
        print """    Possible causes:
           - mismatch of !bc and !ec within one file, such that a !bc
             swallows tex blocks
           - mismatch of !bc and !ec across files in multi-file documents
           - !bt and !et inside code blocks - replace by |bt and |et
    (run doconce on each file to locate the problem, then on
     smaller and smaller parts of each file)"""
        _abort()

    from misc import option
    max_linelength = option('max_bc_linelength=', None)
    if max_linelength is not None:
        max_linelength = int(max_linelength)

        for i in range(len(code_blocks)):
            lines = code_blocks[i].splitlines()
            truncated = False
            for j in range(len(lines)):
                if len(lines[j]) > max_linelength:
                    lines[j] = lines[j][:max_linelength] + '...'
                    truncated = True
            if truncated:
                code_blocks[i] = '\n'.join(lines) + '\n'


    lines = filestr.splitlines()

    # Note: re.sub cannot be used because newlines, \nabla, etc
    # are not handled correctly. Need str.replace.

    for i in range(len(lines)):
        if _CODE_BLOCK in lines[i] or _MATH_BLOCK in lines[i]:
            words = lines[i].split()
            # on a line: number block-indicator code-type
            n = int(words[0])
            if _CODE_BLOCK in lines[i]:
                words[1] = '!bc'
                code = code_blocks[n]
                lines[i] = ' '.join(words[1:]) + '\n' + code + '!ec'
            if _MATH_BLOCK in lines[i]:
                words[1] = '!bc'
                math = tex_blocks[n]
                lines[i] = '!bt\n' + math + '!et'

    filestr = safe_join(lines, '\n')

    # All formats except sphinx and ipynb must remove !bc *hid blocks
    # (maybe html will get the possibility to run hidden blocks)
    if format not in ('sphinx', 'ipynb'):
        filestr = remove_hidden_code_blocks(filestr, format)

    return filestr
Exemple #20
0
def remove_code_and_tex(filestr, format):
    """
    Remove verbatim and latex (math) code blocks from the file and
    store separately in lists (code_blocks and tex_blocks).
    The function insert_code_and_tex will insert these blocks again.
    """
    # Method:
    # store code and tex blocks in lists and substitute these blocks
    # by the contents of _CODE_BLOCK and _MATH_BLOCK (arguments after
    # !bc must be copied after _CODE_BLOCK).
    # later we replace _CODE_BLOCK by !bc and !ec and the code block again
    # (similarly for the tex/math block).

    # ipynb (and future interactive executable documents) needs to
    # see if a code is to be executed or just displayed as text.
    # !bc *cod-t and !bc *pro-t is used to indicate pure text.
    if format not in ('ipynb', 'matlabnb'):
        filestr = re.sub(r'^!bc +([a-z0-9]+)-t', r'!bc \g<1>',
                         filestr, flags=re.MULTILINE)

    # (recall that !bc can be followed by extra information that we must keep:)
    code = re.compile(r'^!bc(.*?)\n(.*?)^!ec *\n', re.DOTALL|re.MULTILINE)

    # Note: final \n is required and may be missing if there is a block
    # at the end of the file, so let us ensure that a blank final
    # line is appended to the text:
    if filestr[-1] != '\n':
        filestr = filestr + '\n'

    result = code.findall(filestr)
    code_blocks = [c for opt, c in result]
    code_block_types = [opt.strip() for opt, c in result]

    tex = re.compile(r'^!bt *\n(.*?)^!et *\n', re.DOTALL|re.MULTILINE)
    tex_blocks = tex.findall(filestr)

    # Remove blocks and substitute by a one-line sign
    filestr = code.sub('%s \g<1>\n' % _CODE_BLOCK, filestr)
    filestr = tex.sub('%s\n' % _MATH_BLOCK, filestr)

    # Number the blocks
    lines = filestr.splitlines()
    code_block_counter = 0
    math_block_counter = 0
    for i in range(len(lines)):
        if lines[i].startswith(_CODE_BLOCK):
            lines[i] = '%d ' % code_block_counter + lines[i]
            code_block_counter += 1
        if lines[i].startswith(_MATH_BLOCK):
            lines[i] = '%d ' % math_block_counter + lines[i]
            math_block_counter += 1
    filestr = safe_join(lines, '\n')

    # Number all equations?
    if option('number_all_equations'):
        subst = [('\\begin{equation*}', '\\begin{equation}'),
                 ('\\end{equation*}', '\\end{equation}'),
                 ('\\[', '\\begin{equation} '),
                 ('\\]', '\\end{equation} '),
                 ('\\begin{align*}', '\\begin{align}'),
                 ('\\end{align*}', '\\end{align}'),
                 ]
    if option('denumber_all_equations'):
        # Remove equation numbers and also labels in those equations
        subst = [('\\begin{equation}', '\\begin{equation*}'),
                 ('\\end{equation}', '\\end{equation*}'),
                 ('\\begin{align}', '\\begin{align*}'),
                 ('\\end{align}', '\\end{align*}'),
                 ]
        removed_labels = []
        for i in range(len(tex_blocks)):
            found = False
            for construction, dummy in subst:
                if construction in tex_blocks[i]:
                    found = True
                    break
            if found:
                for from_, to_ in subst:
                    tex_blocks[i] = tex_blocks[i].replace(from_, to_)
                removed_labels += re.findall(r'label\{(.+?)\}', tex_blocks[i])
                tex_blocks[i] = re.sub(r'label\{.+?\}\n', '', tex_blocks[i])
                tex_blocks[i] = re.sub(r'label\{.+?\}', '', tex_blocks[i])
        all_refs = re.findall(r'ref\{(.+?)\}', filestr)
        problematic_refs = []
        for ref in all_refs:
            if ref in removed_labels:
                problematic_refs.append(ref)
        if problematic_refs:
            print '*** error: removed all equation labels from the DocOnce source,'
            print '    but there are still references (ref{...}) to equation labels:'
            print '\n   ', ', '.join(problematic_refs)
            print '\n    remove all these references!'
            _abort()

    # Give error if blocks contain !bt
    for i in range(len(tex_blocks)):
        if '!bt' in tex_blocks[i] or '!et' in tex_blocks[i]:
            print '*** error: double !bt or !et in latex block:'
            print tex_blocks[i]
            _abort()

    # Check that math blocks do not contain edit markup or comments
    for block in tex_blocks:
        m = re.search(INLINE_TAGS['inlinecomment'], block, flags=re.DOTALL)
        if m:
            print '*** error: tex block with mathematics cannot contain'
            print '    inline comment or edit markup!'
            if m.group('name') in ('del', 'add') or '->' in m.group('comment'):
                # edit markup
                print '    Place info about editing after the block.'
            print block
            _abort()

    # Remove |\pause| in code blocks if not latex
    if format not in ('latex', 'pdflatex'):
        for i in range(len(code_blocks)):
            if r'|\pause|' in code_blocks[i]:
                code_blocks[i] = re.sub(r'^\|\\pause\|\n', '', code_blocks[i], flags=re.MULTILINE)

    return filestr, code_blocks, code_block_types, tex_blocks
Exemple #21
0
def remove_code_and_tex(filestr, format):
    """
    Remove verbatim and latex (math) code blocks from the file and
    store separately in lists (code_blocks and tex_blocks).
    The function insert_code_and_tex will insert these blocks again.
    """
    # Method:
    # store code and tex blocks in lists and substitute these blocks
    # by the contents of _CODE_BLOCK and _MATH_BLOCK (arguments after
    # !bc must be copied after _CODE_BLOCK).
    # later we replace _CODE_BLOCK by !bc and !ec and the code block again
    # (similarly for the tex/math block).

    # ipynb (and future interactive executable documents) needs to
    # see if a code is to be executed or just displayed as text.
    # !bc *cod-t and !bc *pro-t is used to indicate pure text.
    if format not in ('ipynb',):
        filestr = re.sub(r'^!bc +([a-z0-9]+)-t', r'!bc \g<1>',
                         filestr, flags=re.MULTILINE)

    # (recall that !bc can be followed by extra information that we must keep:)
    code = re.compile(r'^!bc(.*?)\n(.*?)^!ec *\n', re.DOTALL|re.MULTILINE)

    # Note: final \n is required and may be missing if there is a block
    # at the end of the file, so let us ensure that a blank final
    # line is appended to the text:
    if filestr[-1] != '\n':
        filestr = filestr + '\n'

    result = code.findall(filestr)
    code_blocks = [c for opt, c in result]
    code_block_types = [opt.strip() for opt, c in result]

    tex = re.compile(r'^!bt *\n(.*?)^!et *\n', re.DOTALL|re.MULTILINE)
    tex_blocks = tex.findall(filestr)

    # Remove blocks and substitute by a one-line sign
    filestr = code.sub('%s \g<1>\n' % _CODE_BLOCK, filestr)
    filestr = tex.sub('%s\n' % _MATH_BLOCK, filestr)

    # Number the blocks
    lines = filestr.splitlines()
    code_block_counter = 0
    math_block_counter = 0
    for i in range(len(lines)):
        if lines[i].startswith(_CODE_BLOCK):
            lines[i] = '%d ' % code_block_counter + lines[i]
            code_block_counter += 1
        if lines[i].startswith(_MATH_BLOCK):
            lines[i] = '%d ' % math_block_counter + lines[i]
            math_block_counter += 1
    filestr = safe_join(lines, '\n')

    # Number all equations?
    if option('number_all_equations'):
        subst = [('\\begin{equation*}', '\\begin{equation}'),
                 ('\\end{equation*}', '\\end{equation}'),
                 ('\\[', '\\begin{equation} '),
                 ('\\]', '\\end{equation} '),
                 ('\\begin{align*}', '\\begin{align}'),
                 ('\\end{align*}', '\\end{align}'),
                 ]
    if option('denumber_all_equations'):
        # Remove equation numbers and also labels in those equations
        subst = [('\\begin{equation}', '\\begin{equation*}'),
                 ('\\end{equation}', '\\end{equation*}'),
                 ('\\begin{align}', '\\begin{align*}'),
                 ('\\end{align}', '\\end{align*}'),
                 ]
        removed_labels = []
        for i in range(len(tex_blocks)):
            found = False
            for construction, dummy in subst:
                if construction in tex_blocks[i]:
                    found = True
                    break
            if found:
                for from_, to_ in subst:
                    tex_blocks[i] = tex_blocks[i].replace(from_, to_)
                removed_labels += re.findall(r'label\{(.+?)\}', tex_blocks[i])
                tex_blocks[i] = re.sub(r'label\{.+?\}\n', '', tex_blocks[i])
                tex_blocks[i] = re.sub(r'label\{.+?\}', '', tex_blocks[i])
        all_refs = re.findall(r'ref\{(.+?)\}', filestr)
        problematic_refs = []
        for ref in all_refs:
            if ref in removed_labels:
                problematic_refs.append(ref)
        if problematic_refs:
            print '*** error: removed all equation labels from the DocOnce source,'
            print '    but there are still references (ref{...}) to equation labels:'
            print '\n   ', ', '.join(problematic_refs)
            print '\n    remove all these references!'
            _abort()

    # Give error if blocks contain !bt
    for i in range(len(tex_blocks)):
        if '!bt' in tex_blocks[i] or '!et' in tex_blocks[i]:
            print '*** error: double !bt or !et in latex block:'
            print tex_blocks[i]
            _abort()

    # Check that math blocks do not contain edit markup or comments
    for block in tex_blocks:
        m = re.search(INLINE_TAGS['inlinecomment'], block, flags=re.DOTALL)
        if m:
            print '*** error: tex block with mathematics cannot contain'
            print '    inline comment or edit markup!'
            if m.group('name') in ('del', 'add') or '->' in m.group('comment'):
                # edit markup
                print '    Place info about editing after the block.'
            print block
            _abort()

    # Remove |\pause| in code blocks if not latex
    if format not in ('latex', 'pdflatex'):
        for i in range(len(code_blocks)):
            if r'|\pause|' in code_blocks[i]:
                code_blocks[i] = re.sub(r'^\|\\pause\|\n', '', code_blocks[i], flags=re.MULTILINE)

    return filestr, code_blocks, code_block_types, tex_blocks
Exemple #22
0
def insert_code_and_tex(filestr, code_blocks, tex_blocks, format,
                        complete_doc=True):
    # Consistency check (only for complete documents):
    # find no of distinct code and math blocks
    # (can be duplicates when solutions are copied at the end)
    pattern = r'^\d+ ' + _CODE_BLOCK
    code_lines = re.findall(pattern, filestr, flags=re.MULTILINE)
    n = len(set(code_lines))
    if complete_doc and len(code_blocks) != n:
        print '*** error: found %d code block markers for %d initial code blocks' % (n, len(code_blocks))
        print """    Possible causes:
           - mismatch of !bt and !et within one file, such that a !bt
             swallows code
           - mismatch of !bt and !et across files in multi-file documents
           - !bc and !ec inside code blocks - replace by |bc and |ec
    (run doconce on each individual file to locate the problem, then on
     smaller and smaller parts of each file)"""
        numbers = range(len(code_blocks))  # expected numbers in code blocks
        for e in code_lines:
            # remove number
            number = int(e.split()[0])
            if number not in numbers:
                print '   Problem: found %s, but the number %d was unexpected' % (e, number)
            else:
                numbers.remove(number)
        if numbers:
            print '    Problem: did not find XX <<<!!CODE_BLOCK for XX in', numbers

        _abort()
    pattern = r'^\d+ ' + _MATH_BLOCK
    n = len(set(re.findall(pattern, filestr, flags=re.MULTILINE)))
    if complete_doc and len(tex_blocks) != n:
        print '*** error: found %d tex block markers for %d initial tex blocks\nAbort!' % (n, len(tex_blocks))
        print """    Possible causes:
           - mismatch of !bc and !ec within one file, such that a !bc
             swallows tex blocks
           - mismatch of !bc and !ec across files in multi-file documents
           - !bt and !et inside code blocks - replace by |bt and |et
    (run doconce on each file to locate the problem, then on
     smaller and smaller parts of each file)"""
        _abort()

    from misc import option
    max_linelength = option('max_bc_linelength=', None)
    if max_linelength is not None:
        max_linelength = int(max_linelength)

        for i in range(len(code_blocks)):
            lines = code_blocks[i].splitlines()
            truncated = False
            for j in range(len(lines)):
                if len(lines[j]) > max_linelength:
                    lines[j] = lines[j][:max_linelength] + '...'
                    truncated = True
            if truncated:
                code_blocks[i] = '\n'.join(lines) + '\n'


    lines = filestr.splitlines()

    # Note: re.sub cannot be used because newlines, \nabla, etc
    # are not handled correctly. Need str.replace.

    for i in range(len(lines)):
        if _CODE_BLOCK in lines[i] or _MATH_BLOCK in lines[i]:
            words = lines[i].split()
            # on a line: number block-indicator code-type
            n = int(words[0])
            if _CODE_BLOCK in lines[i]:
                words[1] = '!bc'
                code = code_blocks[n]
                lines[i] = ' '.join(words[1:]) + '\n' + code + '!ec'
            if _MATH_BLOCK in lines[i]:
                words[1] = '!bc'
                math = tex_blocks[n]
                lines[i] = '!bt\n' + math + '!et'

    filestr = safe_join(lines, '\n')

    # All formats except sphinx and ipynb must remove !bc *hid blocks
    # (maybe html will get the possibility to run hidden blocks)
    if format not in ('sphinx', 'ipynb'):
        filestr = remove_hidden_code_blocks(filestr, format)

    return filestr
Exemple #23
0
def mwiki_figure(m):
    filename = m.group('filename')
    link = filename if filename.startswith('http') else None
    if not link and not os.path.isfile(filename):
        raise IOError('no figure file %s' % filename)

    basename  = os.path.basename(filename)
    stem, ext = os.path.splitext(basename)
    root, ext = os.path.splitext(filename)
    if link is None:
        if not ext in '.png .gif .jpg .jpeg'.split():
            # try to convert image file to PNG, using
            # convert from ImageMagick:
            cmd = 'convert %s png:%s' % (filename, root+'.png')
            failure, output = commands.getstatusoutput(cmd)
            if failure:
                print '\n**** warning: could not run ', cmd
                print '       convert %s to PNG format manually' % filename
                _abort()
            filename = root + '.png'

    caption = m.group('caption').strip()
    if caption != '':
        caption = '|' + caption  # add | for non-empty caption
    else:
        # Avoid filename as caption when caption is empty
        # see http://www.mediawiki.org/wiki/Help:Images
        caption = '|<span title=""></span>'
    # keep label if it's there:
    caption = re.sub(r'label\{(.+?)\}', '(\g<1>)', caption)

    size = ''
    opts = m.group('options').strip()
    if opts:
        info = dict([s.split('=') for s in opts.split()])
        if 'width' in info and 'height' in info:
            size = '|%sx%spx' % (info['width'], info['height'])
        elif 'width' in info:
            size = '|%spx' % info['width']
        elif 'height' in info:
            size = '|x%spx' % info['height']

    if link:
        # We link to some image on the web
        filename = os.path.basename(filename)
        link = os.path.dirname(link)
        result = r"""
[[File:%s|frame%s|link=%s|alt=%s%s]]
""" % (filename, size, link, filename, caption)
    else:
        # We try to link to a file at wikimedia.org.
        found_wikimedia = False
        orig_filename = filename
        # Check if the file exists and find the appropriate wikimedia name.
        # http://en.wikipedia.org/w/api.php?action=query&titles=Image:filename&prop=imageinfo&format=xml

        # Skip directories - get the basename
        filename = os.path.basename(filename)
        import urllib
        prms = urllib.urlencode({
            'action': 'query', 'titles': 'Image:' + filename,
            'prop': 'imageinfo', 'format': 'xml'})
        url = 'http://en.wikipedia.org/w/api.php?' + prms
        try:
            print ' ...checking if %s is stored at en.wikipedia.org/w/api.php...' % filename
            f = urllib.urlopen(url)

            imageinfo = f.read()
            f.close()
            def get_data(name, text):
                pattern = '%s="(.*?)"' % name
                m = re.search(pattern, text)
                if m:
                    match = m.group(1)
                    if 'Image:' in match:
                        return match.split('Image:')[1]
                    if 'File:' in match:
                        return match.split('File:')[1]
                    else:
                        return match
                else:
                    return None

            data = ['from', 'to', 'title', 'missing', 'imagerepository',
                    'timestamp', 'user']
            orig_filename = filename
            filename = get_data('title', imageinfo)
            user = get_data('user', imageinfo)
            timestamp = get_data('timestamp', imageinfo)
            if user:
                found_wikimedia = True
                print ' ...found %s at wikimedia' % filename
                result = r"""
    [[File:%s|frame%s|alt=%s%s]] <!-- user: %s, filename: %s, timestamp: %s -->
    """ % (filename, size, filename, caption, user, orig_filename, timestamp)
        except IOError:
            print ' ...no Internet connection...'

        if not found_wikimedia:
            print ' ...for wikipedia/wikibooks you must upload image file %s to\n    common.wikimedia.org' % orig_filename
            # see http://commons.wikimedia.org/wiki/Commons:Upload
            # and http://commons.wikimedia.org/wiki/Special:UploadWizard
            print ' ...for now we use local file %s' % filename
            # This is fine if we use github wiki

            result = r"""
[[File:%s|frame%s|alt=%s%s]] <!-- not yet uploaded to common.wikimedia.org -->
""" % (filename, size, filename, caption)

    return result
Exemple #24
0
def ipynb_code(filestr, code_blocks, code_block_types, tex_blocks, format):
    """
    # We expand all newcommands now
    from html import embed_newcommands
    newcommands = embed_newcommands(filestr)
    if newcommands:
        filestr = newcommands + filestr
    """
    # Fix pandoc citations to normal internal links: [[key]](#key)
    filestr = re.sub(r'\[@(.+?)\]', r'[[\g<1>]](#\g<1>)', filestr)

    # filestr becomes json list after this function so we must typeset
    # envirs here. All envirs are typeset as pandoc_quote.
    from common import _CODE_BLOCK, _MATH_BLOCK
    envir_format = option('ipynb_admon=', 'paragraph')
    # Remove all !bpop-!epop environments (they cause only problens and
    # have no use)
    for envir in 'pop', 'slidecell':
        filestr = re.sub('^<!-- !b%s .*\n' % envir,
                         '',
                         filestr,
                         flags=re.MULTILINE)
        filestr = re.sub('^<!-- !e%s .*\n' % envir,
                         '',
                         filestr,
                         flags=re.MULTILINE)
    filestr = re.sub('^<!-- !bnotes.*?<!-- !enotes -->\n',
                     '',
                     filestr,
                     flags=re.DOTALL | re.MULTILINE)
    filestr = re.sub('^<!-- !split -->\n', '', filestr, flags=re.MULTILINE)
    from doconce import doconce_envirs
    envirs = doconce_envirs()[8:-2]
    for envir in envirs:
        pattern = r'^!b%s(.*?)\n(.+?)\s*^!e%s' % (envir, envir)
        if envir_format in ('quote', 'paragraph', 'hrule'):

            def subst(m):
                title = m.group(1).strip()
                # Text size specified in parenthesis?
                m2 = re.search('^\s*\((.+?)\)', title)

                if title == '' and envir not in ('block', 'quote'):
                    title = envir.capitalize() + '.'
                elif title.lower() == 'none':
                    title == ''
                elif m2:
                    text_size = m2.group(1).lower()
                    title = title.replace('(%s)' % text_size, '').strip()
                elif title and title[-1] not in ('.', ':', '!', '?'):
                    # Make sure the title ends with puncuation
                    title += '.'
                # Recall that this formatting is called very late
                # so native format must be used!
                if title:
                    title = '**' + title + '**\n'
                    # Could also consider subsubsection formatting
                block = m.group(2)

                # Always use quote typesetting for quotes
                if envir_format == 'quote' or envir == 'quote':
                    # Make Markdown quote of the block: lines start with >
                    lines = []
                    for line in block.splitlines():
                        # Just quote plain text
                        if not (_MATH_BLOCK in line or _CODE_BLOCK in line
                                or line.startswith('FIGURE:')
                                or line.startswith('MOVIE:')
                                or line.startswith('|')):
                            lines.append('> ' + line)
                        else:
                            lines.append('\n' + line + '\n')
                    block = '\n'.join(lines) + '\n\n'

                    # Add quote and a blank line after title
                    if title:
                        title = '> ' + title + '>\n'
                else:
                    # Add a blank line after title
                    if title:
                        title += '\n'

                if envir_format == 'hrule':
                    # Native ------ does not work, use <hr/>
                    #text = '\n\n----------\n' + title + '----------\n' + \
                    #       block + '\n----------\n\n'
                    text = '\n\n<hr/>\n' + title + \
                           block + '\n<hr/>\n\n'
                else:
                    text = title + block + '\n\n'
                return text
        else:
            print '*** error: --ipynb_admon=%s is not supported' % envir_format
        filestr = re.sub(pattern,
                         subst,
                         filestr,
                         flags=re.DOTALL | re.MULTILINE)

    # Fix pyshell and ipy interactive sessions: remove prompt and output.
    # or split in multiple cells such that output comes out at the end of a cell
    # Fix sys environments and use run prog.py so programs can be run in cell
    # Insert %matplotlib inline in the first block using matplotlib
    # Only typeset Python code as blocks, otherwise !bc environmens
    # become plain indented Markdown.
    from doconce import dofile_basename
    from sets import Set
    ipynb_tarfile = 'ipynb-%s-src.tar.gz' % dofile_basename
    src_paths = Set()
    mpl_inline = False

    split_pyshell = option('ipynb_split_pyshell=', 'on')
    if split_pyshell is None:
        split_pyshell = False
    elif split_pyshell in ('no', 'False', 'off'):
        split_pyshell = False
    else:
        split_pyshell = True

    ipynb_code_tp = [None] * len(code_blocks)
    for i in range(len(code_blocks)):
        # Check if continuation lines are in the code block, because
        # doconce.py inserts a blank after the backslash
        if '\\ \n' in code_blocks[i]:
            code_blocks[i] = code_blocks[i].replace('\\ \n', '\\\n')

        if not mpl_inline and (
            re.search(r'import +matplotlib', code_blocks[i]) or \
            re.search(r'from +matplotlib', code_blocks[i]) or \
            re.search(r'import +scitools', code_blocks[i]) or \
            re.search(r'from +scitools', code_blocks[i])):
            code_blocks[i] = '%matplotlib inline\n\n' + code_blocks[i]
            mpl_inline = True

        tp = code_block_types[i]
        if tp.endswith('-t'):
            # Standard Markdown code with pandoc/github extension
            language = tp[:-2]
            language_spec = language2pandoc.get(language, '')
            #code_blocks[i] = '\n' + indent_lines(code_blocks[i], format) + '\n'
            code_blocks[i] = "```%s\n" % language_spec + \
                             indent_lines(code_blocks[i].strip(), format) + \
                             "```"
            ipynb_code_tp[i] = 'markdown'
        elif tp.startswith('pyshell') or tp.startswith('ipy'):
            lines = code_blocks[i].splitlines()
            last_cell_end = -1
            if split_pyshell:
                new_code_blocks = []
                # Split for each output an put in separate cell
                for j in range(len(lines)):
                    if lines[j].startswith('>>>') or lines[j].startswith(
                            '... '):
                        lines[j] = lines[j][4:]
                    elif lines[j].startswith('In ['):  # IPython
                        lines[j] = ':'.join(lines[j].split(':')[1:]).strip()
                    elif lines[j].startswith('   ...: '):  # IPython
                        lines[j] = lines[j][8:]
                    else:
                        # output (no prefix or Out)
                        lines[j] = ''
                        new_code_blocks.append('\n'.join(lines[last_cell_end +
                                                               1:j + 1]))
                        last_cell_end = j
                code_blocks[i] = new_code_blocks
                ipynb_code_tp[i] = 'cell'
            else:
                # Remove prompt and output lines; leave code executable in cell
                for j in range(len(lines)):
                    if lines[j].startswith('>>> ') or lines[j].startswith(
                            '... '):
                        lines[j] = lines[j][4:]
                    elif lines[j].startswith('In ['):
                        lines[j] = ':'.join(lines[j].split(':')[1:]).strip()
                    else:
                        # output
                        lines[j] = ''

                for j in range(lines.count('')):
                    lines.remove('')
                code_blocks[i] = '\n'.join(lines)
                ipynb_code_tp[i] = 'cell'

        elif tp.startswith('sys'):
            # Do we find execution of python file? If so, copy the file
            # to separate subdir and make a run file command in a cell.
            # Otherwise, it is just a plain verbatim Markdown block.
            found_unix_lines = False
            lines = code_blocks[i].splitlines()
            for j in range(len(lines)):
                m = re.search(r'(.+?>|\$) *python +([A-Za-z_0-9]+?\.py)',
                              lines[j])
                if m:
                    name = m.group(2).strip()
                    if os.path.isfile(name):
                        src_paths.add(os.path.dirname(name))
                        lines[j] = '%%run "%s"' % fullpath
                else:
                    found_unix_lines = True
            src_paths = list(src_paths)
            if src_paths and not found_unix_lines:
                # This is a sys block with run commands only
                code_blocks[i] = '\n'.join(lines)
                ipynb_code_tp[i] = 'cell'
            else:
                # Standard Markdown code
                code_blocks[i] = '\n'.join(lines)
                code_blocks[i] = indent_lines(code_blocks[i], format)
                ipynb_code_tp[i] = 'markdown'
        elif tp.endswith('hid'):
            ipynb_code_tp[i] = 'cell_hidden'
        elif tp.startswith('py'):
            ipynb_code_tp[i] = 'cell'
        else:
            # Should support other languages as well, but not for now
            code_blocks[i] = indent_lines(code_blocks[i], format)
            ipynb_code_tp[i] = 'markdown'

    # figure_files and movie_files are global variables and contain
    # all figures and movies referred to
    src_paths = list(src_paths)
    if figure_files:
        src_paths += figure_files
    if movie_files:
        src_paths += movie_files

    if src_paths:
        # Make tar file with all the source dirs with files
        # that need to be executed
        os.system('tar cfz %s %s' % (ipynb_tarfile, ' '.join(src_paths)))
        print 'collected all required additional files in', ipynb_tarfile, 'which must be distributed with the notebook'
    elif os.path.isfile(ipynb_tarfile):
        os.remove(ipynb_tarfile)

    # Parse document into markdown text, code blocks, and tex blocks.
    # Store in nested list notebook_blocks.
    notebook_blocks = [[]]
    authors = ''
    for line in filestr.splitlines():
        if line.startswith('authors = [new_author(name='):  # old author method
            authors = line[10:]
        elif _CODE_BLOCK in line:
            code_block_tp = line.split()[-1]
            if code_block_tp in (
                    'pyhid', ) or not code_block_tp.endswith('hid'):
                notebook_blocks[-1] = '\n'.join(notebook_blocks[-1]).strip()
                notebook_blocks.append(line)
            # else: hidden block to be dropped (may include more languages
            # with time in the above tuple)
        elif _MATH_BLOCK in line:
            notebook_blocks[-1] = '\n'.join(notebook_blocks[-1]).strip()
            notebook_blocks.append(line)
        else:
            if not isinstance(notebook_blocks[-1], list):
                notebook_blocks.append([])
            notebook_blocks[-1].append(line)
    if isinstance(notebook_blocks[-1], list):
        notebook_blocks[-1] = '\n'.join(notebook_blocks[-1]).strip()

    # Add block type info
    pattern = r'(\d+) +%s'
    for i in range(len(notebook_blocks)):
        if re.match(pattern % _CODE_BLOCK, notebook_blocks[i]):
            m = re.match(pattern % _CODE_BLOCK, notebook_blocks[i])
            idx = int(m.group(1))
            if ipynb_code_tp[idx] == 'cell':
                notebook_blocks[i] = ['cell', notebook_blocks[i]]
            elif ipynb_code_tp[idx] == 'cell_hidden':
                notebook_blocks[i] = ['cell_hidden', notebook_blocks[i]]
            else:
                notebook_blocks[i] = ['text', notebook_blocks[i]]
        elif re.match(pattern % _MATH_BLOCK, notebook_blocks[i]):
            notebook_blocks[i] = ['math', notebook_blocks[i]]
        else:
            notebook_blocks[i] = ['text', notebook_blocks[i]]

    # Go through tex_blocks and wrap in $$
    # (doconce.py runs align2equations so there are no align/align*
    # environments in tex blocks)
    label2tag = {}
    tag_counter = 1
    for i in range(len(tex_blocks)):
        # Extract labels and add tags
        labels = re.findall(r'label\{(.+?)\}', tex_blocks[i])
        for label in labels:
            label2tag[label] = tag_counter
            # Insert tag to get labeled equation
            tex_blocks[i] = tex_blocks[i].replace(
                'label{%s}' % label,
                'label{%s} \\tag{%s}' % (label, tag_counter))
            tag_counter += 1

        # Remove \[ and \] or \begin/end{equation*} in single equations
        tex_blocks[i] = tex_blocks[i].replace(r'\[', '')
        tex_blocks[i] = tex_blocks[i].replace(r'\]', '')
        tex_blocks[i] = tex_blocks[i].replace(r'\begin{equation*}', '')
        tex_blocks[i] = tex_blocks[i].replace(r'\end{equation*}', '')
        # Check for illegal environments
        m = re.search(r'\\begin\{(.+?)\}', tex_blocks[i])
        if m:
            envir = m.group(1)
            if envir not in ('equation', 'equation*', 'align*', 'align',
                             'array'):
                print """\
*** warning: latex envir \\begin{%s} does not work well in Markdown.
    Stick to \\[ ... \\], equation, equation*, align, or align*
    environments in math environments.
""" % envir
        eq_type = 'heading'  # or '$$'
        eq_type = '$$'
        # Markdown: add $$ on each side of the equation
        if eq_type == '$$':
            # Make sure there are no newline after equation
            tex_blocks[i] = '$$\n' + tex_blocks[i].strip() + '\n$$'
        # Here: use heading (###) and simple formula (remove newline
        # in math expressions to keep everything within a heading) as
        # the equation then looks bigger
        elif eq_type == 'heading':
            tex_blocks[i] = '### $ ' + '  '.join(
                tex_blocks[i].splitlines()) + ' $'

        # Add labels for the eqs above the block (for reference)
        if labels:
            #label_tp = '<a name="%s"></a>'
            label_tp = '<div id="%s"></div>'
            tex_blocks[i] = '<!-- Equation labels as ordinary links -->\n' + \
                            ' '.join([label_tp % label
                                      for label in labels]) + '\n\n' + \
                                      tex_blocks[i]

    # blocks is now a list of text chunks in markdown and math/code line
    # instructions. Insert code and tex blocks
    for i in range(len(notebook_blocks)):
        if _CODE_BLOCK in notebook_blocks[i][
                1] or _MATH_BLOCK in notebook_blocks[i][1]:
            words = notebook_blocks[i][1].split()
            # start of notebook_blocks[i]: number block-indicator code-type
            n = int(words[0])
            if _CODE_BLOCK in notebook_blocks[i][1]:
                notebook_blocks[i][1] = code_blocks[n]  # can be list!
            if _MATH_BLOCK in notebook_blocks[i][1]:
                notebook_blocks[i][1] = tex_blocks[n]

    # Make IPython structures

    nb_version = int(option('ipynb_version=', '3'))
    if nb_version == 3:
        from IPython.nbformat.v3 import (new_code_cell, new_text_cell,
                                         new_worksheet, new_notebook,
                                         new_metadata, new_author)
        nb = new_worksheet()
    elif nb_version == 4:
        from IPython.nbformat.v4 import (new_code_cell, new_markdown_cell,
                                         new_notebook)
        cells = []

    mdstr = []  # plain md format of the notebook
    prompt_number = 1
    for block_tp, block in notebook_blocks:
        if (block_tp == 'text' or block_tp == 'math') and block != '':
            # Pure comments between math/code and math/code come
            # out as empty blocks, should detect that situation
            # (challenging - can have multiple lines of comments,
            # or begin and end comment lines with important things between)
            if nb_version == 3:
                nb.cells.append(new_text_cell(u'markdown', source=block))
            elif nb_version == 4:
                cells.append(new_markdown_cell(source=block))
            mdstr.append(('markdown', block))
        elif block_tp == 'cell' and block != '' and block != []:
            if isinstance(block, list):
                for block_ in block:
                    block_ = block_.rstrip()
                    if block_ != '':
                        if nb_version == 3:
                            nb.cells.append(
                                new_code_cell(input=block_,
                                              prompt_number=prompt_number,
                                              collapsed=False))
                        elif nb_version == 4:
                            cells.append(
                                new_code_cell(source=block_,
                                              execution_count=prompt_number))
                        prompt_number += 1
                        mdstr.append(('codecell', block_))
            else:
                block = block.rstrip()
                if block != '':
                    if nb_version == 3:
                        nb.cells.append(
                            new_code_cell(input=block,
                                          prompt_number=prompt_number,
                                          collapsed=False))
                    elif nb_version == 4:
                        cells.append(
                            new_code_cell(source=block,
                                          execution_count=prompt_number))
                    prompt_number += 1
                    mdstr.append(('codecell', block))
        elif block_tp == 'cell_hidden' and block != '':
            block = block.rstrip()
            if nb_version == 3:
                nb.cells.append(
                    new_code_cell(input=block,
                                  prompt_number=prompt_number,
                                  collapsed=True))
            elif nb_version == 4:
                cells.append(
                    new_code_cell(source=block, execution_count=prompt_number))
            prompt_number += 1
            mdstr.append(('codecell', block))
    """
    # Dump the notebook cells in a simple ASCII format
    # (doc/src/ipynb/ipynb_generator.py can translate it back to .ipynb file)
    f = open(dofile_basename + '.md-ipynb', 'w')
    for cell_tp, block in mdstr:
        if cell_tp == 'markdown':
            f.write('\n-----\n\n')
        elif cell_tp == 'codecell':
            f.write('\n-----py\n\n')
        f.write(block)
    f.close()
    """

    if nb_version == 3:
        # Catch the title as the first heading
        m = re.search(r'^#+\s*(.+)$', filestr, flags=re.MULTILINE)
        title = m.group(1).strip() if m else ''
        # md below is not used for anything
        if authors:
            authors = eval(authors)
            md = new_metadata(name=title, authors=authors)
        else:
            md = new_metadata(name=title)
        nb = new_notebook(worksheets=[nb], metadata=new_metadata())
        # Let us make v4 notebook here by upgrading
        from IPython.nbformat.v4 import upgrade
        nb = upgrade(nb)
        import IPython.nbformat.v4.nbjson as nbjson

        # Convert nb to json format
        filestr = nbjson.writes(nb)
    elif nb_version == 4:
        nb = new_notebook(cells=cells)
        from IPython.nbformat import writes
        filestr = writes(nb, version=4)

    # Check that there are no empty cells:
    if '"input": []' in filestr:
        print '*** error: empty cells in notebook - report bug in DocOnce'
        _abort()
    # must do the replacements here at the very end when json is written out
    # \eqref and labels will not work, but labels (only in math) do no harm
    filestr = re.sub(r'([^\\])label\{',
                     r'\g<1>\\\\label{',
                     filestr,
                     flags=re.MULTILINE)

    # \\eqref{} just gives (???) link at this stage - future versions
    # will probably support labels
    #filestr = re.sub(r'\(ref\{(.+?)\}\)', r'\\eqref{\g<1>}', filestr)
    # Now we use explicit references to tags
    def subst(m):
        label = m.group(1)
        try:
            return r'[(%s)](#%s)' % (label2tag[label], label)
        except KeyError as e:
            print '*** error: label "%s" is not defined' % str(e)

    filestr = re.sub(r'\(ref\{(.+?)\}\)', subst, filestr)
    """
    # MathJax reference to tag (recall that the equations have both label
    # and tag (know that tag only works well in HTML, but this mjx-eqn-no
    # label does not work in ipynb)
    filestr = re.sub(r'\(ref\{(.+?)\}\)',
                     lambda m: r'[(%s)](#mjx-eqn-%s)' % (label2tag[m.group(1)], label2tag[m.group(1)]), filestr)
    """
    #filestr = re.sub(r'\(ref\{(.+?)\}\)', r'Eq (\g<1>)', filestr)
    '''
    # Final fixes: replace all text between cells by markdown code cells
    # Note: the patterns are overlapping so a plain re.sub will not work,
    # here we run through all blocks found and subsitute the first remaining
    # one, one by one.
    pattern = r'   \},\n(.+?)\{\n    "cell_type":'
    begin_pattern = r'^(.+?)\{\n    "cell_type":'
    remaining_block_begin = re.findall(begin_pattern, filestr, flags=re.DOTALL)
    remaining_blocks = re.findall(pattern, filestr, flags=re.DOTALL)
    import string
    for block in remaining_block_begin + remaining_blocks:
        filestr = string.replace(filestr, block, json_markdown(block) + '   ',
                                 maxreplace=1)
    filestr_end = re.sub(r'   \{\n    "cell_type": .+?\n   \},\n', '', filestr,
                         flags=re.DOTALL)
    filestr = filestr.replace(filestr_end, json_markdown(filestr_end))
    filestr = """{
 "metadata": {
  "name": "SOME NAME"
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
""" + filestr.rstrip() + '\n'+ \
    json_pycode('', final_prompt_no+1, 'python').rstrip()[:-1] + """
   ],
   "metadata": {}
  }
 ]
}"""
    '''
    return filestr
Exemple #25
0
def sphinx_code(filestr, code_blocks, code_block_types, tex_blocks, format):
    # In rst syntax, code blocks are typeset with :: (verbatim)
    # followed by intended blocks. This function indents everything
    # inside code (or TeX) blocks.

    # default mappings of !bc environments and pygments languages:
    envir2pygments = dict(
        cod="python",
        pro="python",
        pycod="python",
        cycod="cython",
        pypro="python",
        cypro="cython",
        fcod="fortran",
        fpro="fortran",
        ccod="c",
        cppcod="c++",
        cpro="c",
        cpppro="c++",
        mcod="matlab",
        mpro="matlab",
        plcod="perl",
        plpro="perl",
        shcod="bash",
        shpro="bash",
        rbcod="ruby",
        rbpro="ruby",
        # sys='console',
        sys="text",
        rst="rst",
        css="css",
        csspro="css",
        csscod="css",
        dat="text",
        csv="text",
        txt="text",
        cc="text",
        ccq="text",  # not possible with extra indent for ccq
        ipy="ipy",
        xmlcod="xml",
        xmlpro="xml",
        xml="xml",
        htmlcod="html",
        htmlpro="html",
        html="html",
        texcod="latex",
        texpro="latex",
        tex="latex",
        latexcod="latex",
        latexpro="latex",
        latex="latex",
        do="doconce",
        pyshell="python",
        pyoptpro="python",
        pyscpro="python",
    )

    # grab line with: # sphinx code-blocks: cod=python cpp=c++ etc
    # (do this before code is inserted in case verbatim blocks contain
    # such specifications for illustration)
    m = re.search(r".. *[Ss]phinx +code-blocks?:(.+)", filestr)
    if m:
        defs_line = m.group(1)
        # turn specifications into a dictionary:
        for definition in defs_line.split():
            key, value = definition.split("=")
            envir2pygments[key] = value

    # First indent all code blocks

    for i in range(len(code_blocks)):
        if code_block_types[i].startswith("pyoptpro") and not option("runestone"):
            code_blocks[i] = online_python_tutor(code_blocks[i], return_tp="iframe")
        if code_block_types[i].endswith("-h"):
            indentation = " " * 8
        else:
            indentation = " " * 4
        code_blocks[i] = indent_lines(code_blocks[i], format, indentation)

    # After transforming align environments to separate equations
    # the problem with math labels in multiple eqs has disappeared.
    # (doconce.py applies align2equations, which takes all align
    # envirs and translates them to separate equations, but align*
    # environments are allowed.
    # Any output of labels in align means an error in the
    # align -> equation transformation...)
    math_labels = []
    multiple_math_labels = []  # sphinx has problems with multiple math labels
    for i in range(len(tex_blocks)):
        tex_blocks[i] = indent_lines(tex_blocks[i], format)
        # extract all \label{}s inside tex blocks and typeset them
        # with :label: tags
        label_regex = fix_latex(r"label\{(.+?)\}", application="match")
        labels = re.findall(label_regex, tex_blocks[i])
        if len(labels) == 1:
            tex_blocks[i] = "   :label: %s\n" % labels[0] + tex_blocks[i]
        elif len(labels) > 1:
            multiple_math_labels.append(labels)
        if len(labels) > 0:
            math_labels.extend(labels)
        tex_blocks[i] = re.sub(label_regex, "", tex_blocks[i])

        # fix latex constructions that do not work with sphinx math
        # (just remove them)
        commands = [
            r"\begin{equation}",
            r"\end{equation}",
            r"\begin{equation*}",
            r"\end{equation*}",
            # r'\begin{eqnarray}',
            # r'\end{eqnarray}',
            # r'\begin{eqnarray*}',
            # r'\end{eqnarray*}',
            # r'\begin{align}',
            # r'\end{align}',
            # r'\begin{align*}',
            # r'\end{align*}',
            r"\begin{multline}",
            r"\end{multline}",
            r"\begin{multline*}",
            r"\end{multline*}",
            # r'\begin{split}',
            # r'\end{split}',
            # r'\begin{gather}',
            # r'\end{gather}',
            # r'\begin{gather*}',
            # r'\end{gather*}',
            r"\[",
            r"\]",
            # some common abbreviations (newcommands):
            r"\beqan",
            r"\eeqan",
            r"\beqa",
            r"\eeqa",
            r"\balnn",
            r"\ealnn",
            r"\baln",
            r"\ealn",
            r"\beq",
            r"\eeq",  # the simplest name, contained in others, must come last!
        ]
        for command in commands:
            tex_blocks[i] = tex_blocks[i].replace(command, "")
        # &=& -> &=
        tex_blocks[i] = re.sub("&\s*=\s*&", " &= ", tex_blocks[i])
        # provide warnings for problematic environments

    # Replace all references to equations that have labels in math environments:
    for label in math_labels:
        filestr = filestr.replace("(:ref:`%s`)" % label, ":eq:`%s`" % label)

    multiple_math_labels_with_refs = []  # collect the labels with references
    for labels in multiple_math_labels:
        for label in labels:
            ref = ":eq:`%s`" % label  # ref{} is translated to eq:``
            if ref in filestr:
                multiple_math_labels_with_refs.append(label)

    if multiple_math_labels_with_refs:
        errwarn(
            """
*** warning: detected non-align math environment with multiple labels
    (Sphinx cannot handle this equation system - labels will be removed
    and references to them will be empty):"""
        )
        for label in multiple_math_labels_with_refs:
            errwarn("    label{%s}" % label)
        print

    filestr = insert_code_and_tex(filestr, code_blocks, tex_blocks, "sphinx")

    # Remove all !bc ipy and !bc pyshell since interactive sessions
    # are automatically handled by sphinx without indentation
    # (just a blank line before and after)
    filestr = re.sub(r"^!bc +d?ipy *\n(.*?)^!ec *\n", "\n\g<1>\n", filestr, re.DOTALL | re.MULTILINE)
    filestr = re.sub(r"^!bc +d?pyshell *\n(.*?)^!ec *\n", "\n\g<1>\n", filestr, re.DOTALL | re.MULTILINE)

    # Check if we have custom pygments lexers
    if "ipy" in code_block_types:
        if not has_custom_pygments_lexer("ipy"):
            envir2pygments["ipy"] = "python"
    if "do" in code_block_types:
        if not has_custom_pygments_lexer("doconce"):
            envir2pygments["do"] = "text"

    # Make correct code-block:: language constructions
    legal_pygments_languages = get_legal_pygments_lexers()
    for key in set(code_block_types):
        if key in envir2pygments:
            if not envir2pygments[key] in legal_pygments_languages:
                errwarn(
                    """*** warning: %s is not a legal Pygments language (lexer)
found in line:
  %s

    The 'text' lexer will be used instead.
"""
                    % (envir2pygments[key], defs_line)
                )
                envir2pygments[key] = "text"

        # filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
        #                 '\n.. code-block:: %s\n\n' % envir2pygments[key], filestr,
        #                 flags=re.MULTILINE)

        # Check that we have code installed to handle pyscpro
        if "pyscpro" in filestr and key == "pyscpro":
            try:
                import icsecontrib.sagecellserver
            except ImportError:
                errwarn(
                    """
*** warning: pyscpro for computer code (sage cells) is requested, but'
    icsecontrib.sagecellserver from https://github.com/kriskda/sphinx-sagecell
    is not installed. Using plain Python typesetting instead."""
                )
                key = "pypro"

        if key == "pyoptpro":
            if option("runestone"):
                filestr = re.sub(
                    r"^!bc\s+%s\s*\n" % key,
                    "\n.. codelens:: codelens_\n   :showoutput:\n\n",
                    filestr,
                    flags=re.MULTILINE,
                )
            else:
                filestr = re.sub(r"^!bc\s+%s\s*\n" % key, "\n.. raw:: html\n\n", filestr, flags=re.MULTILINE)
        elif key == "pyscpro":
            if option("runestone"):
                filestr = re.sub(
                    r"^!bc\s+%s\s*\n" % key,
                    """
.. activecode:: activecode_
   :language: python

""",
                    filestr,
                    flags=re.MULTILINE,
                )
            else:
                filestr = re.sub(r"^!bc\s+%s\s*\n" % key, "\n.. sagecellserver::\n\n", filestr, flags=re.MULTILINE)
        elif key == "pysccod":
            if option("runestone"):
                # Include (i.e., run) all previous code segments...
                # NOTE: this is most likely not what we want
                include = ", ".join([i for i in range(1, activecode_counter)])
                filestr = re.sub(
                    r"^!bc\s+%s\s*\n" % key,
                    """
.. activecode:: activecode_
   :language: python
   "include: %s
"""
                    % include,
                    filestr,
                    flags=re.MULTILINE,
                )
            else:
                errwarn(
                    "*** error: pysccod for sphinx is not supported without the --runestone flag\n    (but pyscpro is via Sage Cell Server)"
                )
                _abort()

        elif key == "":
            # any !bc with/without argument becomes a text block:
            filestr = re.sub(r"^!bc$", "\n.. code-block:: text\n\n", filestr, flags=re.MULTILINE)
        elif key.endswith("hid"):
            if key in ("pyhid", "jshid", "htmlhid") and option("runestone"):
                # Allow runestone books to run hidden code blocks
                # (replace pyhid by pycod, then remove all !bc *hid)
                for i in range(len(code_block_types)):
                    if code_block_types[i] == key:
                        code_block_types[i] = key.replace("hid", "cod")

                key2language = dict(py="python", js="javascript", html="html")
                language = key2language[key.replace("hid", "")]
                include = ", ".join([i for i in range(1, activecode_counter)])
                filestr = re.sub(
                    r"^!bc +%s\s*\n" % key,
                    """
.. activecode:: activecode_
   :language: %s
   :include: %s
   :hidecode:

"""
                    % (language, include),
                    filestr,
                    flags=re.MULTILINE,
                )
            else:
                # Remove hidden code block
                pattern = r"^!bc +%s\n.+?^!ec" % key
                filestr = re.sub(pattern, "", filestr, flags=re.MULTILINE | re.DOTALL)
        else:
            show_hide = False
            if key.endswith("-h"):
                key_orig = key
                key = key[:-2]
                show_hide = True
            # Use the standard sphinx code-block directive
            if key in envir2pygments:
                pygments_language = envir2pygments[key]
            elif key in legal_pygments_languages:
                pygments_language = key
            else:
                errwarn('*** error: detected code environment "%s"' % key)
                errwarn("    which is not registered in sphinx.py (sphinx_code)")
                errwarn("    or not a language registered in pygments")
                _abort()
            if show_hide:
                filestr = re.sub(
                    r"^!bc +%s\s*\n" % key_orig,
                    "\n.. container:: toggle\n\n    .. container:: header\n\n        **Show/Hide Code**\n\n    .. code-block:: %s\n\n"
                    % pygments_language,
                    filestr,
                    flags=re.MULTILINE,
                )
                # Must add 4 indent in corresponding code_blocks[i], done above
            else:
                filestr = re.sub(
                    r"^!bc +%s\s*\n" % key, "\n.. code-block:: %s\n\n" % pygments_language, filestr, flags=re.MULTILINE
                )

    # any !bc with/without argument becomes a text block:
    filestr = re.sub(r"^!bc.*$", "\n.. code-block:: text\n\n", filestr, flags=re.MULTILINE)
    filestr = re.sub(r"^!ec *\n", "\n", filestr, flags=re.MULTILINE)
    # filestr = re.sub(r'^!ec\n', '\n', filestr, flags=re.MULTILINE)
    # filestr = re.sub(r'^!ec\n', '', filestr, flags=re.MULTILINE)

    filestr = re.sub(r"^!bt *\n", "\n.. math::\n", filestr, flags=re.MULTILINE)
    filestr = re.sub(r"^!et *\n", "\n", filestr, flags=re.MULTILINE)
    # Fix lacking blank line after :label:
    filestr = re.sub(r"^(   :label: .+?)(\n *[^ ]+)", r"\g<1>\n\n\g<2>", filestr, flags=re.MULTILINE)

    # Insert counters for runestone blocks
    if option("runestone"):
        codelens_counter = 0
        activecode_counter = 0
        lines = filestr.splitlines()
        for i in range(len(lines)):
            if ".. codelens:: codelens_" in lines[i]:
                codelens_counter += 1
                lines[i] = lines[i].replace("codelens_", "codelens_%d" % codelens_counter)
            if ".. activecode:: activecode_" in lines[i]:
                activecode_counter += 1
                lines[i] = lines[i].replace("activecode_", "activecode_%d" % activecode_counter)
        filestr = "\n".join(lines)

    # Final fixes

    filestr = fix_underlines_in_headings(filestr)
    # Ensure blank line before and after comments
    filestr = re.sub(r"([.:;?!])\n^\.\. ", r"\g<1>\n\n.. ", filestr, flags=re.MULTILINE)
    filestr = re.sub(r"(^\.\. .+)\n([^ \n]+)", r"\g<1>\n\n\g<2>", filestr, flags=re.MULTILINE)

    # Line breaks interfer with tables and needs a final blank line too
    lines = filestr.splitlines()
    inside_block = False
    for i in range(len(lines)):
        if lines[i].startswith("<linebreakpipe>") and not inside_block:
            inside_block = True
            lines[i] = lines[i].replace("<linebreakpipe> ", "") + "\n"
            continue
        if lines[i].startswith("<linebreakpipe>") and inside_block:
            lines[i] = "|" + lines[i].replace("<linebreakpipe>", "")
            continue
        if inside_block and not lines[i].startswith("<linebreakpipe>"):
            inside_block = False
            lines[i] = "| " + lines[i] + "\n"
    filestr = "\n".join(lines)

    # Remove double !split (TOC with a prefix !split gives two !splits)
    pattern = "^.. !split\s+.. !split"
    filestr = re.sub(pattern, ".. !split", filestr, flags=re.MULTILINE)

    if option("html_links_in_new_window"):
        # Insert a comment to be recognized by automake_sphinx.py such that it
        # can replace the default links by proper modified target= option.
        # filestr = '\n\n.. NOTE: Open external links in new windows.\n\n' + filestr
        # Use JavaScript instead
        filestr = (
            """.. raw:: html

        <script type="text/javascript">
        $(document).ready(function() {
            $("a[href^='http']").attr('target','_blank');
        });
        </script>

"""
            + filestr
        )

    # Remove too much vertical space
    filestr = re.sub(r"\n{3,}", "\n\n", filestr)

    return filestr
Exemple #26
0
def sphinx_code(filestr, code_blocks, code_block_types, tex_blocks, format):
    # In rst syntax, code blocks are typeset with :: (verbatim)
    # followed by intended blocks. This function indents everything
    # inside code (or TeX) blocks.

    # default mappings of !bc environments and pygments languages:
    envir2pygments = dict(
        cod='python',
        pro='python',
        pycod='python',
        cycod='cython',
        pypro='python',
        cypro='cython',
        fcod='fortran',
        fpro='fortran',
        ccod='c',
        cppcod='c++',
        cpro='c',
        cpppro='c++',
        mcod='matlab',
        mpro='matlab',
        plcod='perl',
        plpro='perl',
        shcod='bash',
        shpro='bash',
        rbcod='ruby',
        rbpro='ruby',
        #sys='console',
        sys='text',
        rst='rst',
        css='css',
        csspro='css',
        csscod='css',
        dat='text',
        csv='text',
        txt='text',
        cc='text',
        ccq='text',  # not possible with extra indent for ccq
        ipy='ipy',
        xmlcod='xml',
        xmlpro='xml',
        xml='xml',
        htmlcod='html',
        htmlpro='html',
        html='html',
        texcod='latex',
        texpro='latex',
        tex='latex',
        latexcod='latex',
        latexpro='latex',
        latex='latex',
        do='doconce',
        pyshell='python',
        pyoptpro='python',
        pyscpro='python',
    )

    # grab line with: # sphinx code-blocks: cod=python cpp=c++ etc
    # (do this before code is inserted in case verbatim blocks contain
    # such specifications for illustration)
    m = re.search(r'.. *[Ss]phinx +code-blocks?:(.+)', filestr)
    if m:
        defs_line = m.group(1)
        # turn specifications into a dictionary:
        for definition in defs_line.split():
            key, value = definition.split('=')
            envir2pygments[key] = value

    # First indent all code blocks

    for i in range(len(code_blocks)):
        if code_block_types[i].startswith(
                'pyoptpro') and not option('runestone'):
            code_blocks[i] = online_python_tutor(code_blocks[i],
                                                 return_tp='iframe')
        if code_block_types[i].endswith('-h'):
            indentation = ' ' * 8
        else:
            indentation = ' ' * 4
        code_blocks[i] = indent_lines(code_blocks[i], format, indentation)

    # After transforming align environments to separate equations
    # the problem with math labels in multiple eqs has disappeared.
    # (doconce.py applies align2equations, which takes all align
    # envirs and translates them to separate equations, but align*
    # environments are allowed.
    # Any output of labels in align means an error in the
    # align -> equation transformation...)
    math_labels = []
    multiple_math_labels = []  # sphinx has problems with multiple math labels
    for i in range(len(tex_blocks)):
        tex_blocks[i] = indent_lines(tex_blocks[i], format)
        # extract all \label{}s inside tex blocks and typeset them
        # with :label: tags
        label_regex = fix_latex(r'label\{(.+?)\}', application='match')
        labels = re.findall(label_regex, tex_blocks[i])
        if len(labels) == 1:
            tex_blocks[i] = '   :label: %s\n' % labels[0] + tex_blocks[i]
        elif len(labels) > 1:
            multiple_math_labels.append(labels)
        if len(labels) > 0:
            math_labels.extend(labels)
        tex_blocks[i] = re.sub(label_regex, '', tex_blocks[i])

        # fix latex constructions that do not work with sphinx math
        # (just remove them)
        commands = [
            r'\begin{equation}',
            r'\end{equation}',
            r'\begin{equation*}',
            r'\end{equation*}',
            #r'\begin{eqnarray}',
            #r'\end{eqnarray}',
            #r'\begin{eqnarray*}',
            #r'\end{eqnarray*}',
            #r'\begin{align}',
            #r'\end{align}',
            #r'\begin{align*}',
            #r'\end{align*}',
            r'\begin{multline}',
            r'\end{multline}',
            r'\begin{multline*}',
            r'\end{multline*}',
            #r'\begin{split}',
            #r'\end{split}',
            #r'\begin{gather}',
            #r'\end{gather}',
            #r'\begin{gather*}',
            #r'\end{gather*}',
            r'\[',
            r'\]',
            # some common abbreviations (newcommands):
            r'\beqan',
            r'\eeqan',
            r'\beqa',
            r'\eeqa',
            r'\balnn',
            r'\ealnn',
            r'\baln',
            r'\ealn',
            r'\beq',
            r'\eeq',  # the simplest name, contained in others, must come last!
        ]
        for command in commands:
            tex_blocks[i] = tex_blocks[i].replace(command, '')
        # &=& -> &=
        tex_blocks[i] = re.sub('&\s*=\s*&', ' &= ', tex_blocks[i])
        # provide warnings for problematic environments

    # Replace all references to equations that have labels in math environments:
    for label in math_labels:
        filestr = filestr.replace('(:ref:`%s`)' % label, ':eq:`%s`' % label)

    multiple_math_labels_with_refs = []  # collect the labels with references
    for labels in multiple_math_labels:
        for label in labels:
            ref = ':eq:`%s`' % label  # ref{} is translated to eq:``
            if ref in filestr:
                multiple_math_labels_with_refs.append(label)

    if multiple_math_labels_with_refs:
        errwarn("""
*** warning: detected non-align math environment with multiple labels
    (Sphinx cannot handle this equation system - labels will be removed
    and references to them will be empty):""")
        for label in multiple_math_labels_with_refs:
            errwarn('    label{%s}' % label)
        print

    filestr = insert_code_and_tex(filestr, code_blocks, tex_blocks, 'sphinx')

    # Remove all !bc ipy and !bc pyshell since interactive sessions
    # are automatically handled by sphinx without indentation
    # (just a blank line before and after)
    filestr = re.sub(r'^!bc +d?ipy *\n(.*?)^!ec *\n', '\n\g<1>\n', filestr,
                     re.DOTALL | re.MULTILINE)
    filestr = re.sub(r'^!bc +d?pyshell *\n(.*?)^!ec *\n', '\n\g<1>\n', filestr,
                     re.DOTALL | re.MULTILINE)

    # Check if we have custom pygments lexers
    if 'ipy' in code_block_types:
        if not has_custom_pygments_lexer('ipy'):
            envir2pygments['ipy'] = 'python'
    if 'do' in code_block_types:
        if not has_custom_pygments_lexer('doconce'):
            envir2pygments['do'] = 'text'

    # Make correct code-block:: language constructions
    legal_pygments_languages = get_legal_pygments_lexers()
    for key in set(code_block_types):
        if key in envir2pygments:
            if not envir2pygments[key] in legal_pygments_languages:
                errwarn(
                    """*** warning: %s is not a legal Pygments language (lexer)
found in line:
  %s

    The 'text' lexer will be used instead.
""" % (envir2pygments[key], defs_line))
                envir2pygments[key] = 'text'

        #filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
        #                 '\n.. code-block:: %s\n\n' % envir2pygments[key], filestr,
        #                 flags=re.MULTILINE)

        # Check that we have code installed to handle pyscpro
        if 'pyscpro' in filestr and key == 'pyscpro':
            try:
                import icsecontrib.sagecellserver
            except ImportError:
                errwarn("""
*** warning: pyscpro for computer code (sage cells) is requested, but'
    icsecontrib.sagecellserver from https://github.com/kriskda/sphinx-sagecell
    is not installed. Using plain Python typesetting instead.""")
                key = 'pypro'

        if key == 'pyoptpro':
            if option('runestone'):
                filestr = re.sub(
                    r'^!bc\s+%s\s*\n' % key,
                    '\n.. codelens:: codelens_\n   :showoutput:\n\n',
                    filestr,
                    flags=re.MULTILINE)
            else:
                filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
                                 '\n.. raw:: html\n\n',
                                 filestr,
                                 flags=re.MULTILINE)
        elif key == 'pyscpro':
            if option('runestone'):
                filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
                                 """
.. activecode:: activecode_
   :language: python

""",
                                 filestr,
                                 flags=re.MULTILINE)
            else:
                filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
                                 '\n.. sagecellserver::\n\n',
                                 filestr,
                                 flags=re.MULTILINE)
        elif key == 'pysccod':
            if option('runestone'):
                # Include (i.e., run) all previous code segments...
                # NOTE: this is most likely not what we want
                include = ', '.join([i for i in range(1, activecode_counter)])
                filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
                                 """
.. activecode:: activecode_
   :language: python
   "include: %s
""" % include,
                                 filestr,
                                 flags=re.MULTILINE)
            else:
                errwarn(
                    '*** error: pysccod for sphinx is not supported without the --runestone flag\n    (but pyscpro is via Sage Cell Server)'
                )
                _abort()

        elif key == '':
            # any !bc with/without argument becomes a text block:
            filestr = re.sub(r'^!bc$',
                             '\n.. code-block:: text\n\n',
                             filestr,
                             flags=re.MULTILINE)
        elif key.endswith('hid'):
            if key in ('pyhid', 'jshid', 'htmlhid') and option('runestone'):
                # Allow runestone books to run hidden code blocks
                # (replace pyhid by pycod, then remove all !bc *hid)
                for i in range(len(code_block_types)):
                    if code_block_types[i] == key:
                        code_block_types[i] = key.replace('hid', 'cod')

                key2language = dict(py='python', js='javascript', html='html')
                language = key2language[key.replace('hid', '')]
                include = ', '.join([i for i in range(1, activecode_counter)])
                filestr = re.sub(r'^!bc +%s\s*\n' % key,
                                 """
.. activecode:: activecode_
   :language: %s
   :include: %s
   :hidecode:

""" % (language, include),
                                 filestr,
                                 flags=re.MULTILINE)
            else:
                # Remove hidden code block
                pattern = r'^!bc +%s\n.+?^!ec' % key
                filestr = re.sub(pattern,
                                 '',
                                 filestr,
                                 flags=re.MULTILINE | re.DOTALL)
        else:
            show_hide = False
            if key.endswith('-h'):
                key_orig = key
                key = key[:-2]
                show_hide = True
            # Use the standard sphinx code-block directive
            if key in envir2pygments:
                pygments_language = envir2pygments[key]
            elif key in legal_pygments_languages:
                pygments_language = key
            else:
                errwarn('*** error: detected code environment "%s"' % key)
                errwarn(
                    '    which is not registered in sphinx.py (sphinx_code)')
                errwarn('    or not a language registered in pygments')
                _abort()
            if show_hide:
                filestr = re.sub(r'^!bc +%s\s*\n' % key_orig,
                                 '\n.. container:: toggle\n\n    .. container:: header\n\n        **Show/Hide Code**\n\n    .. code-block:: %s\n\n' % \
                                 pygments_language, filestr, flags=re.MULTILINE)
                # Must add 4 indent in corresponding code_blocks[i], done above
            else:
                filestr = re.sub(r'^!bc +%s\s*\n' % key,
                                 '\n.. code-block:: %s\n\n' % \
                                 pygments_language, filestr, flags=re.MULTILINE)

    # any !bc with/without argument becomes a text block:
    filestr = re.sub(r'^!bc.*$',
                     '\n.. code-block:: text\n\n',
                     filestr,
                     flags=re.MULTILINE)
    filestr = re.sub(r'^!ec *\n', '\n', filestr, flags=re.MULTILINE)
    #filestr = re.sub(r'^!ec\n', '\n', filestr, flags=re.MULTILINE)
    #filestr = re.sub(r'^!ec\n', '', filestr, flags=re.MULTILINE)

    filestr = re.sub(r'^!bt *\n', '\n.. math::\n', filestr, flags=re.MULTILINE)
    filestr = re.sub(r'^!et *\n', '\n', filestr, flags=re.MULTILINE)
    # Fix lacking blank line after :label:
    filestr = re.sub(r'^(   :label: .+?)(\n *[^ ]+)',
                     r'\g<1>\n\n\g<2>',
                     filestr,
                     flags=re.MULTILINE)

    # Insert counters for runestone blocks
    if option('runestone'):
        codelens_counter = 0
        activecode_counter = 0
        lines = filestr.splitlines()
        for i in range(len(lines)):
            if '.. codelens:: codelens_' in lines[i]:
                codelens_counter += 1
                lines[i] = lines[i].replace('codelens_',
                                            'codelens_%d' % codelens_counter)
            if '.. activecode:: activecode_' in lines[i]:
                activecode_counter += 1
                lines[i] = lines[i].replace(
                    'activecode_', 'activecode_%d' % activecode_counter)
        filestr = '\n'.join(lines)

    # Final fixes

    filestr = fix_underlines_in_headings(filestr)
    # Ensure blank line before and after comments
    filestr = re.sub(r'([.:;?!])\n^\.\. ',
                     r'\g<1>\n\n.. ',
                     filestr,
                     flags=re.MULTILINE)
    filestr = re.sub(r'(^\.\. .+)\n([^ \n]+)',
                     r'\g<1>\n\n\g<2>',
                     filestr,
                     flags=re.MULTILINE)

    # Line breaks interfer with tables and needs a final blank line too
    lines = filestr.splitlines()
    inside_block = False
    for i in range(len(lines)):
        if lines[i].startswith('<linebreakpipe>') and not inside_block:
            inside_block = True
            lines[i] = lines[i].replace('<linebreakpipe> ', '') + '\n'
            continue
        if lines[i].startswith('<linebreakpipe>') and inside_block:
            lines[i] = '|' + lines[i].replace('<linebreakpipe>', '')
            continue
        if inside_block and not lines[i].startswith('<linebreakpipe>'):
            inside_block = False
            lines[i] = '| ' + lines[i] + '\n'
    filestr = '\n'.join(lines)

    # Remove double !split (TOC with a prefix !split gives two !splits)
    pattern = '^.. !split\s+.. !split'
    filestr = re.sub(pattern, '.. !split', filestr, flags=re.MULTILINE)

    if option('html_links_in_new_window'):
        # Insert a comment to be recognized by automake_sphinx.py such that it
        # can replace the default links by proper modified target= option.
        #filestr = '\n\n.. NOTE: Open external links in new windows.\n\n' + filestr
        # Use JavaScript instead
        filestr = """.. raw:: html

        <script type="text/javascript">
        $(document).ready(function() {
            $("a[href^='http']").attr('target','_blank');
        });
        </script>

""" + filestr

    # Remove too much vertical space
    filestr = re.sub(r'\n{3,}', '\n\n', filestr)

    return filestr
Exemple #27
0
def ipynb_figure(m):
    # m.group() must be called before m.group('name')
    text = '<!-- dom:%s -->\n<!-- begin figure -->\n' % m.group()

    filename = m.group('filename')
    caption = m.group('caption').strip()
    opts = m.group('options').strip()
    if opts:
        info = [s.split('=') for s in opts.split()]
        opts = ' '.join([
            '%s=%s' % (opt, value) for opt, value in info
            if opt not in ['frac', 'sidecap']
        ])

    global figure_files
    if not filename.startswith('http'):
        figure_files.append(filename)

    # Extract optional label in caption
    label = None
    pattern = r' *label\{(.+?)\}'
    m = re.search(pattern, caption)
    if m:
        label = m.group(1).strip()
        caption = re.sub(pattern, '', caption)

    display_method = option('ipynb_figure=', 'imgtag')
    if display_method == 'md':
        # Markdown image syntax for embedded image in text
        # (no control of size, then one must use HTML syntax)
        if label is not None:
            #text += '<a name="%s"></a>\n' % label
            text += '<div id="%s"></div>\n' % label
        text += '![%s](%s)' % (caption, filename)
    elif display_method == 'imgtag':
        # Plain <img tag, allows specifying the image size
        if label is not None:
            #text += '<a name="%s"></a>' % label
            text += '<div id="%s"></div>\n' % label
        text += """
<p>%s</p>
<img src="%s" %s>

""" % (caption, filename, opts)
    elif display_method == 'Image':
        # Image object
        # NOTE: This code will normally not work because it inserts a verbatim
        # block in the file *after* all such blocks have been removed and
        # numbered. doconce.py makes a test prior to removal of blocks and
        # runs the handle_figures and movie substitution if ipynb format
        # and Image or movie object display.
        text += '\n'
        if label is not None:
            text += '<div id="%s"></div>' % label
        text += '<!-- options: %s -->\n' % opts
        text = '!bc pycod\n'
        global figure_encountered
        if not figure_encountered:
            # First time we have a figure, we must import Image
            text += 'from IPython.display import Image\n'
            figure_encountered = True
        if caption:
            text += '# ' + caption
        if filename.startswith('http'):
            keyword = 'url'
        else:
            keyword = 'filename'
        text += 'Image(%s="%s")\n' % (keyword, filename)
        text += '!ec\n'
    else:
        print '*** error: --ipynb_figure=%s is illegal, must be md, imgtag or Image' % display_method
        _abort()
    text += '<!-- end figure -->\n'
    return text
Exemple #28
0
def sphinx_quiz_runestone(quiz):
    quiz_feedback = option('quiz_explanations=', 'on')

    text = ''
    if 'new page' in quiz:
        text += '.. !split\n%s\n%s' % (quiz['new page'],
                                       '-' * len(quiz['new page']))

    text += '.. begin quiz\n\n'
    global question_counter
    question_counter += 1
    # Multiple correct answers?
    if sum([1 for choice in quiz['choices'] if choice[0] == 'right']) > 1:
        text += '.. mchoicema:: question_%d' % question_counter + '\n'
    else:
        text += '.. mchoicemf:: question_%d' % question_counter + '\n'

    def fix_text(s, tp='answer'):
        """
        Answers and feedback in RunestoneInteractive book quizzes
        cannot contain math, figure and rst markup. Perform fixes.
        """
        drop = False
        if 'math::' in s:
            errwarn('\n*** warning: quiz %s with math block not supported:' %
                    tp)
            errwarn(s)
            drop = True
        if '.. code-block::' in s:
            errwarn('\n*** warning: quiz %s with code block not supported:' %
                    tp)
            errwarn(s)
            drop = True
        if '.. figure::' in s:
            errwarn('\n*** warning: quiz %s with figure not supported:' % tp)
            errwarn(s)
            drop = True
        if drop:
            return ''
        # Make multi-line paragraph a one-liner
        s = ' '.join(s.splitlines()).rstrip()
        # Fixes
        pattern = r'`(.+?) (<https?.+?)>`__'  # URL
        s = re.sub(pattern, '<a href="\g<2>"> \g<1> </a>', s)
        pattern = r'``(.+?)``'  # verbatim
        s = re.sub(pattern, '<tt>\g<1></tt>', s)
        pattern = r':math:`(.+?)`'  # inline math
        s = re.sub(pattern, '<em>\g<1></em>', s)  # mimic italic....
        pattern = r':\*(.+?)\*'  # emphasize
        s = re.sub(pattern, '\g<1>', s, flags=re.DOTALL)
        return s

    import string
    correct = []
    for i, choice in enumerate(quiz['choices']):
        if i > 4:  # not supported
            errwarn(
                '*** warning: quiz with %d choices gets truncated (first 5)' %
                len(quiz['choices']))
            break
        letter = string.ascii_lowercase[i]
        text += '   :answer_%s: ' % letter
        answer = fix_text(choice[1], tp='answer')
        if not answer:
            answer = 'Too advanced typesetting prevents the text from being rendered'
        text += answer + '\n'
        if choice[0] == 'right':
            correct.append(letter)
    if correct:
        text += '   :correct: ' + ', '.join(correct) + '\n'
    else:
        errwarn(
            '*** error: correct choice in quiz has index > 5 (max 5 allowed for RunestoneInteractive books)'
        )
        errwarn(quiz['question'])
        _abort()
    for i, choice in enumerate(quiz['choices']):
        if i > 4:  # not supported
            break
        letter = string.ascii_lowercase[i]
        text += '   :feedback_%s: ' % letter  # must be present
        if len(choice) == 3 and quiz_feedback == 'on':
            feedback = fix_text(choice[2], tp='explanation')
            if not feedback:
                feedback = '(Too advanced typesetting prevents the text from being rendered)'
            text += feedback
        text += '\n'

    text += '\n' + indent_lines(quiz['question'], 'sphinx', ' ' * 3) + '\n\n\n'
    return text
Exemple #29
0
def mwiki_figure(m):
    filename = m.group('filename')
    link = filename if filename.startswith('http') else None
    if not link and not os.path.isfile(filename):
        raise IOError('no figure file %s' % filename)

    basename = os.path.basename(filename)
    stem, ext = os.path.splitext(basename)
    root, ext = os.path.splitext(filename)
    if link is None:
        if not ext in '.png .gif .jpg .jpeg'.split():
            # try to convert image file to PNG, using
            # convert from ImageMagick:
            cmd = 'convert %s png:%s' % (filename, root + '.png')
            try:
                output = subprocess.check_output(cmd,
                                                 shell=True,
                                                 stderr=subprocess.STDOUT)
            except subprocess.CalledProcessError as e:
                errwarn('\n**** warning: could not run ' + cmd)
                errwarn('       convert %s to PNG format manually' % filename)
                _abort()
            filename = root + '.png'

    caption = m.group('caption').strip()
    if caption != '':
        caption = '|' + caption  # add | for non-empty caption
    else:
        # Avoid filename as caption when caption is empty
        # see http://www.mediawiki.org/wiki/Help:Images
        caption = '|<span title=""></span>'
    # keep label if it's there:
    caption = re.sub(r'label\{(.+?)\}', '(\g<1>)', caption)

    size = ''
    opts = m.group('options').strip()
    if opts:
        info = dict([s.split('=') for s in opts.split()])
        if 'width' in info and 'height' in info:
            size = '|%sx%spx' % (info['width'], info['height'])
        elif 'width' in info:
            size = '|%spx' % info['width']
        elif 'height' in info:
            size = '|x%spx' % info['height']

    if link:
        # We link to some image on the web
        filename = os.path.basename(filename)
        link = os.path.dirname(link)
        result = r"""
[[File:%s|frame%s|link=%s|alt=%s%s]]
""" % (filename, size, link, filename, caption)
    else:
        # We try to link to a file at wikimedia.org.
        found_wikimedia = False
        orig_filename = filename
        # Check if the file exists and find the appropriate wikimedia name.
        # http://en.wikipedia.org/w/api.php?action=query&titles=Image:filename&prop=imageinfo&format=xml

        # Skip directories - get the basename
        filename = os.path.basename(filename)
        import urllib
        prms = urllib.urlencode({
            'action': 'query',
            'titles': 'Image:' + filename,
            'prop': 'imageinfo',
            'format': 'xml'
        })
        url = 'http://en.wikipedia.org/w/api.php?' + prms
        try:
            errwarn(
                ' ...checking if %s is stored at en.wikipedia.org/w/api.php...'
                % filename)
            f = urllib.urlopen(url)

            imageinfo = f.read()
            f.close()

            def get_data(name, text):
                pattern = '%s="(.*?)"' % name
                m = re.search(pattern, text)
                if m:
                    match = m.group(1)
                    if 'Image:' in match:
                        return match.split('Image:')[1]
                    if 'File:' in match:
                        return match.split('File:')[1]
                    else:
                        return match
                else:
                    return None

            data = [
                'from', 'to', 'title', 'missing', 'imagerepository',
                'timestamp', 'user'
            ]
            orig_filename = filename
            filename = get_data('title', imageinfo)
            user = get_data('user', imageinfo)
            timestamp = get_data('timestamp', imageinfo)
            if user:
                found_wikimedia = True
                errwarn(' ...found %s at wikimedia' % filename)
                result = r"""
    [[File:%s|frame%s|alt=%s%s]] <!-- user: %s, filename: %s, timestamp: %s -->
    """ % (filename, size, filename, caption, user, orig_filename, timestamp)
        except IOError:
            errwarn(' ...no Internet connection...')

        if not found_wikimedia:
            errwarn(
                ' ...for wikipedia/wikibooks you must upload image file %s to\n    common.wikimedia.org'
                % orig_filename)
            # see http://commons.wikimedia.org/wiki/Commons:Upload
            # and http://commons.wikimedia.org/wiki/Special:UploadWizard
            errwarn(' ...for now we use local file %s' % filename)
            # This is fine if we use github wiki

            result = r"""
[[File:%s|frame%s|alt=%s%s]] <!-- not yet uploaded to common.wikimedia.org -->
""" % (filename, size, filename, caption)

    return result
Exemple #30
0
def sphinx_code(filestr, code_blocks, code_block_types,
                tex_blocks, format):
    # In rst syntax, code blocks are typeset with :: (verbatim)
    # followed by intended blocks. This function indents everything
    # inside code (or TeX) blocks.

    # default mappings of !bc environments and pygments languages:
    envir2pygments = dict(
        cod='python', pro='python',
        pycod='python', cycod='cython',
        pypro='python', cypro='cython',
        fcod='fortran', fpro='fortran',
        ccod='c', cppcod='c++',
        cpro='c', cpppro='c++',
        mcod='matlab', mpro='matlab',
        plcod='perl', plpro='perl',
        shcod='bash', shpro='bash',
        rbcod='ruby', rbpro='ruby',
        #sys='console',
        sys='text',
        rst='rst',
        css='css', csspro='css', csscod='css',
        dat='text', csv='text', txt='text',
        cc='text', ccq='text',  # not possible with extra indent for ccq
        ipy='ipy',
        xmlcod='xml', xmlpro='xml', xml='xml',
        htmlcod='html', htmlpro='html', html='html',
        texcod='latex', texpro='latex', tex='latex',
        latexcod='latex', latexpro='latex', latex='latex',
        do='doconce',
        pyshell='python',
        pyoptpro='python', pyscpro='python',
        )

    # grab line with: # Sphinx code-blocks: cod=python cpp=c++ etc
    # (do this before code is inserted in case verbatim blocks contain
    # such specifications for illustration)
    m = re.search(r'.. *[Ss]phinx +code-blocks?:(.+)', filestr)
    if m:
        defs_line = m.group(1)
        # turn specifications into a dictionary:
        for definition in defs_line.split():
            key, value = definition.split('=')
            envir2pygments[key] = value

    # First indent all code blocks

    for i in range(len(code_blocks)):
        if code_block_types[i].startswith('pyoptpro') and not option('runestone'):
            code_blocks[i] = online_python_tutor(code_blocks[i],
                                                 return_tp='iframe')
        code_blocks[i] = indent_lines(code_blocks[i], format)

    # After transforming align environments to separate equations
    # the problem with math labels in multiple eqs has disappeared.
    # (doconce.py applies align2equations, which takes all align
    # envirs and translates them to separate equations, but align*
    # environments are allowed.
    # Any output of labels in align means an error in the
    # align -> equation transformation...)
    math_labels = []
    multiple_math_labels = []  # sphinx has problems with multiple math labels
    for i in range(len(tex_blocks)):
        tex_blocks[i] = indent_lines(tex_blocks[i], format)
        # extract all \label{}s inside tex blocks and typeset them
        # with :label: tags
        label_regex = fix_latex( r'label\{(.+?)\}', application='match')
        labels = re.findall(label_regex, tex_blocks[i])
        if len(labels) == 1:
            tex_blocks[i] = '   :label: %s\n' % labels[0] + tex_blocks[i]
        elif len(labels) > 1:
            multiple_math_labels.append(labels)
        if len(labels) > 0:
            math_labels.extend(labels)
        tex_blocks[i] = re.sub(label_regex, '', tex_blocks[i])

        # fix latex constructions that do not work with sphinx math
        commands = [r'\begin{equation}',
                    r'\end{equation}',
                    r'\begin{equation*}',
                    r'\end{equation*}',
                    r'\begin{eqnarray}',
                    r'\end{eqnarray}',
                    r'\begin{eqnarray*}',
                    r'\end{eqnarray*}',
                    r'\begin{align}',
                    r'\end{align}',
                    r'\begin{align*}',
                    r'\end{align*}',
                    r'\begin{multline}',
                    r'\end{multline}',
                    r'\begin{multline*}',
                    r'\end{multline*}',
                    r'\begin{split}',
                    r'\end{split}',
                    r'\begin{gather}',
                    r'\end{gather}',
                    r'\begin{gather*}',
                    r'\end{gather*}',
                    r'\[',
                    r'\]',
                    # some common abbreviations (newcommands):
                    r'\beqan',
                    r'\eeqan',
                    r'\beqa',
                    r'\eeqa',
                    r'\balnn',
                    r'\ealnn',
                    r'\baln',
                    r'\ealn',
                    r'\beq',
                    r'\eeq',  # the simplest, contained in others, must come last!
                    ]
        for command in commands:
            tex_blocks[i] = tex_blocks[i].replace(command, '')
        tex_blocks[i] = re.sub('&\s*=\s*&', ' &= ', tex_blocks[i])
        # provide warnings for problematic environments
        if '{alignat' in tex_blocks[i]:
            print '*** warning: the "alignat" environment will give errors in Sphinx:\n\n', tex_blocks[i], '\n'

    # Replace all references to equations that have labels in math environments:
    for label in math_labels:
        filestr = filestr.replace('(:ref:`%s`)' % label, ':eq:`%s`' % label)

    multiple_math_labels_with_refs = [] # collect the labels with references
    for labels in multiple_math_labels:
        for label in labels:
            ref = ':eq:`%s`' % label  # ref{} is translated to eq:``
            if ref in filestr:
                multiple_math_labels_with_refs.append(label)

    if multiple_math_labels_with_refs:
        print """
*** warning: detected non-align math environment with multiple labels
    (Sphinx cannot handle this equation system - labels will be removed
    and references to them will be empty):"""
        for label in multiple_math_labels_with_refs:
            print '    label{%s}' % label
        print

    filestr = insert_code_and_tex(filestr, code_blocks, tex_blocks, 'sphinx')

    # Remove all !bc ipy and !bc pyshell since interactive sessions
    # are automatically handled by sphinx without indentation
    # (just a blank line before and after)
    filestr = re.sub(r'^!bc +d?ipy *\n(.*?)^!ec *\n',
                     '\n\g<1>\n', filestr, re.DOTALL|re.MULTILINE)
    filestr = re.sub(r'^!bc +d?pyshell *\n(.*?)^!ec *\n',
                     '\n\g<1>\n', filestr, re.DOTALL|re.MULTILINE)

    # Check if we have custom pygments lexers
    if 'ipy' in code_block_types:
        if not has_custom_pygments_lexer('ipy'):
            envir2pygments['ipy'] = 'python'
    if 'do' in code_block_types:
        if not has_custom_pygments_lexer('doconce'):
            envir2pygments['do'] = 'text'

    # Make correct code-block:: language constructions
    legal_pygments_languages = get_legal_pygments_lexers()
    for key in set(code_block_types):
        if key in envir2pygments:
            if not envir2pygments[key] in legal_pygments_languages:
                print """*** warning: %s is not a legal Pygments language (lexer)
found in line:
  %s

    The 'text' lexer will be used instead.
""" % (envir2pygments[key], defs_line)
                envir2pygments[key] = 'text'

        #filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
        #                 '\n.. code-block:: %s\n\n' % envir2pygments[key], filestr,
        #                 flags=re.MULTILINE)

        # Check that we have code installed to handle pyscpro
        if 'pyscpro' in filestr and key == 'pyscpro':
            try:
                import icsecontrib.sagecellserver
            except ImportError:
                print """
*** warning: pyscpro for computer code (sage cells) is requested, but'
    icsecontrib.sagecellserver from https://github.com/kriskda/sphinx-sagecell
    is not installed. Using plain Python typesetting instead."""
                key = 'pypro'

        if key == 'pyoptpro':
            if option('runestone'):
                filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
                    '\n.. codelens:: codelens_\n   :showoutput:\n\n',
                    filestr, flags=re.MULTILINE)
            else:
                filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
                                 '\n.. raw:: html\n\n',
                                 filestr, flags=re.MULTILINE)
        elif key == 'pyscpro':
            if option('runestone'):
                filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
                                 """
.. activecode:: activecode_
   :language: python

""", filestr, flags=re.MULTILINE)
            else:
                filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
                                 '\n.. sagecellserver::\n\n',
                                 filestr, flags=re.MULTILINE)
        elif key == 'pysccod':
            if option('runestone'):
                # Include (i.e., run) all previous code segments...
                # NOTE: this is most likely not what we want
                include = ', '.join([i for i in range(1, activecode_counter)])
                filestr = re.sub(r'^!bc\s+%s\s*\n' % key,
                                 """
.. activecode:: activecode_
   :language: python
   "include: %s
""" % include, filestr, flags=re.MULTILINE)
            else:
                print '*** error: pysccod for sphinx is not supported without the --runestone flag\n    (but pyscpro is via Sage Cell Server)'
                _abort()

        elif key == '':
            # any !bc with/without argument becomes a text block:
            filestr = re.sub(r'^!bc$', '\n.. code-block:: text\n\n', filestr,
                             flags=re.MULTILINE)
        elif key.endswith('hid'):
            if key in ('pyhid', 'jshid', 'htmlhid') and option('runestone'):
                # Allow runestone books to run hidden code blocks
                # (replace pyhid by pycod, then remove all !bc *hid)
                for i in range(len(code_block_types)):
                    if code_block_types[i] == key:
                        code_block_types[i] = key.replace('hid', 'cod')

                key2language = dict(py='python', js='javascript', html='html')
                language = key2language[key.replace('hid', '')]
                include = ', '.join([i for i in range(1, activecode_counter)])
                filestr = re.sub(r'^!bc +%s\s*\n' % key,
                                 """
.. activecode:: activecode_
   :language: %s
   :include: %s
   :hidecode:

""" % (language, include), filestr, flags=re.MULTILINE)
            else:
                # Remove hidden code block
                pattern = r'^!bc +%s\n.+?^!ec' % key
                filestr = re.sub(pattern, '', filestr,
                                 flags=re.MULTILINE|re.DOTALL)
        else:
            # Use the standard sphinx code-block directive
            if key in envir2pygments:
                pygments_language = envir2pygments[key]
            elif key in legal_pygments_languages:
                pygments_language = key
            else:
                print '*** error: detected code environment "%s"' % key
                print '    which is not registered in sphinx.py (sphinx_code)'
                print '    or not a language registered in pygments'
                _abort()
            filestr = re.sub(r'^!bc +%s\s*\n' % key,
                             '\n.. code-block:: %s\n\n' % \
                             pygments_language, filestr, flags=re.MULTILINE)

    # any !bc with/without argument becomes a text block:
    filestr = re.sub(r'^!bc.*$', '\n.. code-block:: text\n\n', filestr,
                     flags=re.MULTILINE)
    filestr = re.sub(r'^!ec *\n', '\n', filestr, flags=re.MULTILINE)
    #filestr = re.sub(r'^!ec\n', '\n', filestr, flags=re.MULTILINE)
    #filestr = re.sub(r'^!ec\n', '', filestr, flags=re.MULTILINE)
    filestr = re.sub(r'^!bt *\n', '\n.. math::\n', filestr, flags=re.MULTILINE)
    filestr = re.sub(r'^!et *\n', '\n', filestr, flags=re.MULTILINE)

    # Insert counters for runestone blocks
    if option('runestone'):
        codelens_counter = 0
        activecode_counter = 0
        lines = filestr.splitlines()
        for i in range(len(lines)):
            if '.. codelens:: codelens_' in lines[i]:
                codelens_counter += 1
                lines[i] = lines[i].replace('codelens_', 'codelens_%d' %
                                            codelens_counter)
            if '.. activecode:: activecode_' in lines[i]:
                activecode_counter += 1
                lines[i] = lines[i].replace('activecode_', 'activecode_%d' %
                                            activecode_counter)
        filestr = '\n'.join(lines)


    # Final fixes

    filestr = fix_underlines_in_headings(filestr)
    # Ensure blank line before and after comments
    filestr = re.sub(r'([.:;?!])\n^\.\. ', r'\g<1>\n\n.. ',
                     filestr, flags=re.MULTILINE)
    filestr = re.sub(r'(^\.\. .+)\n([^ \n]+)', r'\g<1>\n\n\g<2>',
                     filestr, flags=re.MULTILINE)
    # Line breaks interfer with tables and needs a final blank line too
    lines = filestr.splitlines()
    inside_block = False
    for i in range(len(lines)):
        if lines[i].startswith('<linebreakpipe>') and not inside_block:
            inside_block = True
            lines[i] = lines[i].replace('<linebreakpipe> ', '') + '\n'
            continue
        if lines[i].startswith('<linebreakpipe>') and inside_block:
            lines[i] = '|' + lines[i].replace('<linebreakpipe>', '')
            continue
        if inside_block and not lines[i].startswith('<linebreakpipe>'):
            inside_block = False
            lines[i] = '| ' + lines[i] + '\n'
    filestr = '\n'.join(lines)

    # Remove double !split (TOC with a prefix !split gives two !splits)
    pattern = '^.. !split\s+.. !split'
    filestr = re.sub(pattern, '.. !split', filestr, flags=re.MULTILINE)

    if option('html_links_in_new_window'):
        # Insert a comment to be recognized by automake_sphinx.py such that it
        # can replace the default links by proper modified target= option.
        #filestr = '\n\n.. NOTE: Open external links in new windows.\n\n' + filestr
        # Use JavaScript instead
        filestr = """.. raw:: html

        <script type="text/javascript">
        $(document).ready(function() {
            $("a[href^='http']").attr('target','_blank');
        });
        </script>

""" + filestr


    # Remove too much vertical space
    filestr = re.sub(r'\n{3,}', '\n\n', filestr)

    return filestr
Exemple #31
0
def ipynb_movie(m):
    # m.group() must be called before m.group('name')
    text = '<!-- dom:%s -->' % m.group()

    global html_encountered, movie_encountered, movie_files
    filename = m.group('filename')
    caption = m.group('caption').strip()
    youtube = False

    if 'youtu.be' in filename or 'youtube.com' in filename:
        youtube = True
    if '*' in filename or '->' in filename:
        errwarn('*** warning: * or -> in movie filenames is not supported in ipynb')
        return text

    def YouTubeVideo(filename):
        # Use YouTubeVideo object
        if 'watch?v=' in filename:
            name = filename.split('watch?v=')[1]
        elif 'youtu.be/' in filename:
            name = filename.split('youtu.be/')[1]
        else:
            errwarn('*** error: youtube movie name "%s" could not be interpreted' % filename)
            _abort()

        text = ''
        global movie_encountered
        if not movie_encountered:
            text += 'from IPython.display import YouTubeVideo\n'
            movie_encountered = True
        text += 'YouTubeVideo("%s")\n' % name
        return text

    text += '\n<!-- begin movie -->\n'
    display_method = option('ipynb_movie=', 'HTML')
    if display_method == 'md':
        text += html_movie(m)
    elif display_method.startswith('HTML'):
        text += '\n!bc pycod\n'
        if youtube and 'YouTube' in display_method:
            text += YouTubeVideo(filename)
            if caption:
                text += '\nprint "%s"' % caption
        else:
            # Use HTML formatting
            if not html_encountered:
                text += 'from IPython.display import HTML\n'
                html_encountered = True
            text += '_s = """' + html_movie(m) + '"""\n'
            text += 'HTML(_s)\n'
            if not filename.startswith('http'):
                movie_files.append(filename)
        text += '!ec\n'
    elif display_method == 'ipynb':
        text += '!bc pycod\n'
        if youtube:
            text += YouTubeVideo(filename)
            if caption:
                text += '\nprint "%s"' % caption
        else:
            # see http://nbviewer.ipython.org/github/ipython/ipython/blob/1.x/examples/notebooks/Part%205%20-%20Rich%20Display%20System.ipynb
            # http://stackoverflow.com/questions/18019477/how-can-i-play-a-local-video-in-my-ipython-notebook
            # http://python.6.x6.nabble.com/IPython-User-embedding-non-YouTube-movies-in-the-IPython-notebook-td5024035.html
            # Just support .mp4, .ogg, and.webm
            stem, ext = os.path.splitext(filename)
            if ext not in ('.mp4', '.ogg', '.webm'):
                errwarn('*** error: movie "%s" in format %s is not supported for --ipynb_movie=%s' % (filename, ext, display_method))
                errwarn('    use --ipynb_movie=HTML instead')
                _abort()
            height = 365
            width = 640
            if filename.startswith('http'):
                file_open = 'import urllib\nvideo = urllib.urlopen("%s").read()' % filename
            else:
                file_open = 'video = open("%s", "rb").read()' % filename
            text += """
%s
from base64 import b64encode
video_encoded = b64encode(video)
video_tag = '<video controls loop alt="%s" height="%s" width="%s" src="data:video/%s;base64,{0}">'.format(video_encoded)
""" % (file_open, filename, height, width, ext[1:])
            if not filename.startswith('http'):
                movie_files.append(filename)
            if not html_encountered:
                text += 'from IPython.display import HTML\n'
                html_encountered = True
            text += 'HTML(data=video_tag)\n'
            if caption:
                text += '\nprint "%s"' % caption
        text += '!ec\n'
    else:
        errwarn('*** error: --ipynb_movie=%s is not supported' % display_method)
        _abort()
    text += '<!-- end movie -->\n'
    return text
Exemple #32
0
def ipynb_movie(m):
    # m.group() must be called before m.group('name')
    text = '<!-- dom:%s -->' % m.group()

    global html_encountered, movie_encountered, movie_files
    filename = m.group('filename')
    caption = m.group('caption').strip()
    youtube = False

    if 'youtu.be' in filename or 'youtube.com' in filename:
        youtube = True
    if '*' in filename or '->' in filename:
        print '*** warning: * or -> in movie filenames is not supported in ipynb'
        return text

    def YouTubeVideo(filename):
        # Use YouTubeVideo object
        if 'watch?v=' in filename:
            name = filename.split('watch?v=')[1]
        elif 'youtu.be/' in filename:
            name = filename.split('youtu.be/')[1]
        else:
            print '*** error: youtube movie name "%s" could not be interpreted' % filename
            _abort()

        text = ''
        global movie_encountered
        if not movie_encountered:
            text += 'from IPython.display import YouTubeVideo\n'
            movie_encountered = True
        text += 'YouTubeVideo("%s")\n' % name
        return text

    text += '\n<!-- begin movie -->\n'
    display_method = option('ipynb_movie=', 'HTML')
    if display_method == 'md':
        text += html_movie(m)
    elif display_method.startswith('HTML'):
        text += '\n!bc pycod\n'
        if youtube and 'YouTube' in display_method:
            text += YouTubeVideo(filename)
            if caption:
                text += '\nprint "%s"' % caption
        else:
            # Use HTML formatting
            if not html_encountered:
                text += 'from IPython.display import HTML\n'
                html_encountered = True
            text += '_s = """' + html_movie(m) + '"""\n'
            text += 'HTML(_s)\n'
            if not filename.startswith('http'):
                movie_files.append(filename)
        text += '!ec\n'
    elif display_method == 'ipynb':
        text += '!bc pycod\n'
        if youtube:
            text += YouTubeVideo(filename)
            if caption:
                text += '\nprint "%s"' % caption
        else:
            # see http://nbviewer.ipython.org/github/ipython/ipython/blob/1.x/examples/notebooks/Part%205%20-%20Rich%20Display%20System.ipynb
            # http://stackoverflow.com/questions/18019477/how-can-i-play-a-local-video-in-my-ipython-notebook
            # http://python.6.x6.nabble.com/IPython-User-embedding-non-YouTube-movies-in-the-IPython-notebook-td5024035.html
            # Just support .mp4, .ogg, and.webm
            stem, ext = os.path.splitext(filename)
            if ext not in ('.mp4', '.ogg', '.webm'):
                print '*** error: movie "%s" in format %s is not supported for --ipynb_movie=%s' % (
                    filename, ext, display_method)
                print '    use --ipynb_movie=HTML instead'
                _abort()
            height = 365
            width = 640
            if filename.startswith('http'):
                file_open = 'import urllib\nvideo = urllib.urlopen("%s").read()' % filename
            else:
                file_open = 'video = open("%s", "rb").read()' % filename
            text += """
%s
from base64 import b64encode
video_encoded = b64encode(video)
video_tag = '<video controls loop alt="%s" height="%s" width="%s" src="data:video/%s;base64,{0}">'.format(video_encoded)
""" % (file_open, filename, height, width, ext[1:])
            if not filename.startswith('http'):
                movie_files.append(filename)
            if not html_encountered:
                text += 'from IPython.display import HTML\n'
                html_encountered = True
            text += 'HTML(data=video_tag)\n'
            if caption:
                text += '\nprint "%s"' % caption
        text += '!ec\n'
    else:
        print '*** error: --ipynb_movie=%s is not supported' % display_method
        _abort()
    text += '<!-- end movie -->\n'
    return text
Exemple #33
0
def sphinx_quiz_runestone(quiz):
    quiz_feedback = option('quiz_explanations=', 'on')

    text = ''
    if 'new page' in quiz:
        text += '.. !split\n%s\n%s' % (quiz['new page'], '-'*len(quiz['new page']))

    text += '.. begin quiz\n\n'
    global question_counter
    question_counter += 1
    # Multiple correct answers?
    if sum([1 for choice in quiz['choices'] if choice[0] == 'right']) > 1:
        text += '.. mchoicema:: question_%d' % question_counter + '\n'
    else:
        text += '.. mchoicemf:: question_%d' % question_counter + '\n'

    def fix_text(s, tp='answer'):
        """
        Answers and feedback in RunestoneInteractive book quizzes
        cannot contain math, figure and rst markup. Perform fixes.
        """
        drop = False
        if 'math::' in s:
            print '\n*** warning: quiz %s with math block not supported:' % tp
            print s
            drop = True
        if '.. code-block::' in s:
            print '\n*** warning: quiz %s with code block not supported:' % tp
            print s
            drop = True
        if '.. figure::' in s:
            print '\n*** warning: quiz %s with figure not supported:' % tp
            print s
            drop = True
        if drop:
            return ''
        # Make multi-line paragraph a one-liner
        s = ' '.join(s.splitlines()).rstrip()
        # Fixes
        pattern = r'`(.+?) (<https?.+?)>`__'  # URL
        s = re.sub(pattern, '<a href="\g<2>"> \g<1> </a>', s)
        pattern = r'``(.+?)``'  # verbatim
        s = re.sub(pattern, '<tt>\g<1></tt>', s)
        pattern = r':math:`(.+?)`'  # inline math
        s = re.sub(pattern, '<em>\g<1></em>', s)  # mimic italic....
        pattern = r':\*(.+?)\*'  # emphasize
        s = re.sub(pattern, '\g<1>', s, flags=re.DOTALL)
        return s

    import string
    correct = []
    for i, choice in enumerate(quiz['choices']):
        if i > 4:  # not supported
            print '*** warning: quiz with %d choices gets truncated (first 5)' % len(quiz['choices'])
            break
        letter = string.ascii_lowercase[i]
        text += '   :answer_%s: ' % letter
        answer = fix_text(choice[1], tp='answer')
        if not answer:
            answer = 'Too advanced typesetting prevents the text from being rendered'
        text += answer + '\n'
        if choice[0] == 'right':
            correct.append(letter)
    if correct:
        text += '   :correct: ' + ', '.join(correct) + '\n'
    else:
        print '*** error: correct choice in quiz has index > 5 (max 5 allowed for RunestoneInteractive books)'
        print quiz['question']
        _abort()
    for i, choice in enumerate(quiz['choices']):
        if i > 4:  # not supported
            break
        letter = string.ascii_lowercase[i]
        text += '   :feedback_%s: ' % letter  # must be present
        if len(choice) == 3 and quiz_feedback == 'on':
            feedback = fix_text(choice[2], tp='explanation')
            if not feedback:
                feedback = '(Too advanced typesetting prevents the text from being rendered)'
            text += feedback
        text += '\n'

    text += '\n' + indent_lines(quiz['question'], 'sphinx', ' '*3) + '\n\n\n'
    return text
Exemple #34
0
def ipynb_code(filestr, code_blocks, code_block_types,
               tex_blocks, format):
    """
    # We expand all newcommands now
    from html import embed_newcommands
    newcommands = embed_newcommands(filestr)
    if newcommands:
        filestr = newcommands + filestr
    """
    # Fix pandoc citations to normal internal links: [[key]](#key)
    filestr = re.sub(r'\[@(.+?)\]', r'[[\g<1>]](#\g<1>)', filestr)

    # filestr becomes json list after this function so we must typeset
    # envirs here. All envirs are typeset as pandoc_quote.
    from common import _CODE_BLOCK, _MATH_BLOCK
    envir_format = option('ipynb_admon=', 'paragraph')
    # Remove all !bpop-!epop environments (they cause only problens and
    # have no use)
    for envir in 'pop', 'slidecell':
        filestr = re.sub('^<!-- !b%s .*\n' % envir, '', filestr,
                         flags=re.MULTILINE)
        filestr = re.sub('^<!-- !e%s .*\n' % envir, '', filestr,
                         flags=re.MULTILINE)
    filestr = re.sub('^<!-- !bnotes.*?<!-- !enotes -->\n', '', filestr,
                     flags=re.DOTALL|re.MULTILINE)
    filestr = re.sub('^<!-- !split -->\n', '', filestr, flags=re.MULTILINE)
    from doconce import doconce_envirs
    envirs = doconce_envirs()[8:-2]
    for envir in envirs:
        pattern = r'^!b%s(.*?)\n(.+?)\s*^!e%s' % (envir, envir)
        if envir_format in ('quote', 'paragraph', 'hrule'):
            def subst(m):
                title = m.group(1).strip()
                # Text size specified in parenthesis?
                m2 = re.search('^\s*\((.+?)\)', title)

                if title == '' and envir not in ('block', 'quote'):
                    title = envir.capitalize() + '.'
                elif title.lower() == 'none':
                    title == ''
                elif m2:
                    text_size = m2.group(1).lower()
                    title = title.replace('(%s)' % text_size, '').strip()
                elif title and title[-1] not in ('.', ':', '!', '?'):
                    # Make sure the title ends with puncuation
                    title += '.'
                # Recall that this formatting is called very late
                # so native format must be used!
                if title:
                    title = '**' + title + '**\n'
                    # Could also consider subsubsection formatting
                block = m.group(2)

                # Always use quote typesetting for quotes
                if envir_format == 'quote' or envir == 'quote':
                    # Make Markdown quote of the block: lines start with >
                    lines = []
                    for line in block.splitlines():
                        # Just quote plain text
                        if not (_MATH_BLOCK in line or
                                _CODE_BLOCK in line or
                                line.startswith('FIGURE:') or
                                line.startswith('MOVIE:') or
                                line.startswith('|')):
                            lines.append('> ' + line)
                        else:
                            lines.append('\n' + line + '\n')
                    block = '\n'.join(lines) + '\n\n'

                    # Add quote and a blank line after title
                    if title:
                        title = '> ' + title + '>\n'
                else:
                    # Add a blank line after title
                    if title:
                        title += '\n'

                if envir_format == 'hrule':
                    # Native ------ does not work, use <hr/>
                    #text = '\n\n----------\n' + title + '----------\n' + \
                    #       block + '\n----------\n\n'
                    text = '\n\n<hr/>\n' + title + \
                           block + '\n<hr/>\n\n'
                else:
                    text = title + block + '\n\n'
                return text
        else:
            errwarn('*** error: --ipynb_admon=%s is not supported'  % envir_format)
        filestr = re.sub(pattern, subst, filestr,
                         flags=re.DOTALL | re.MULTILINE)

    # Fix pyshell and ipy interactive sessions: remove prompt and output.
    # or split in multiple cells such that output comes out at the end of a cell
    # Fix sys environments and use run prog.py so programs can be run in cell
    # Insert %matplotlib inline in the first block using matplotlib
    # Only typeset Python code as blocks, otherwise !bc environmens
    # become plain indented Markdown.
    from doconce import dofile_basename
    from sets import Set
    ipynb_tarfile = 'ipynb-%s-src.tar.gz' % dofile_basename
    src_paths = Set()
    mpl_inline = False

    split_pyshell = option('ipynb_split_pyshell=', 'on')
    if split_pyshell is None:
        split_pyshell = False
    elif split_pyshell in ('no', 'False', 'off'):
        split_pyshell = False
    else:
        split_pyshell = True

    ipynb_code_tp = [None]*len(code_blocks)
    for i in range(len(code_blocks)):
        # Check if continuation lines are in the code block, because
        # doconce.py inserts a blank after the backslash
        if '\\ \n' in code_blocks[i]:
            code_blocks[i] = code_blocks[i].replace('\\ \n', '\\\n')

        if not mpl_inline and (
            re.search(r'import +matplotlib', code_blocks[i]) or \
            re.search(r'from +matplotlib', code_blocks[i]) or \
            re.search(r'import +scitools', code_blocks[i]) or \
            re.search(r'from +scitools', code_blocks[i])):
            code_blocks[i] = '%matplotlib inline\n\n' + code_blocks[i]
            mpl_inline = True

        tp = code_block_types[i]
        if tp.endswith('-t'):
            # Standard Markdown code with pandoc/github extension
            language = tp[:-2]
            language_spec = language2pandoc.get(language, '')
            #code_blocks[i] = '\n' + indent_lines(code_blocks[i], format) + '\n'
            code_blocks[i] = "```%s\n" % language_spec + \
                             indent_lines(code_blocks[i].strip(), format) + \
                             "```"
            ipynb_code_tp[i] = 'markdown'
        elif tp.startswith('pyshell') or tp.startswith('ipy'):
            lines = code_blocks[i].splitlines()
            last_cell_end = -1
            if split_pyshell:
                new_code_blocks = []
                # Split for each output an put in separate cell
                for j in range(len(lines)):
                    if lines[j].startswith('>>>') or lines[j].startswith('... '):
                        lines[j] = lines[j][4:]
                    elif lines[j].startswith('In ['):  # IPython
                        lines[j] = ':'.join(lines[j].split(':')[1:]).strip()
                    elif lines[j].startswith('   ...: '): # IPython
                        lines[j] = lines[j][8:]
                    else:
                        # output (no prefix or Out)
                        lines[j] = ''
                        new_code_blocks.append(
                            '\n'.join(lines[last_cell_end+1:j+1]))
                        last_cell_end = j
                code_blocks[i] = new_code_blocks
                ipynb_code_tp[i] = 'cell'
            else:
                # Remove prompt and output lines; leave code executable in cell
                for j in range(len(lines)):
                    if lines[j].startswith('>>> ') or lines[j].startswith('... '):
                        lines[j] = lines[j][4:]
                    elif lines[j].startswith('In ['):
                        lines[j] = ':'.join(lines[j].split(':')[1:]).strip()
                    else:
                        # output
                        lines[j] = ''

                for j in range(lines.count('')):
                    lines.remove('')
                code_blocks[i] = '\n'.join(lines)
                ipynb_code_tp[i] = 'cell'

        elif tp.startswith('sys'):
            # Do we find execution of python file? If so, copy the file
            # to separate subdir and make a run file command in a cell.
            # Otherwise, it is just a plain verbatim Markdown block.
            found_unix_lines = False
            lines = code_blocks[i].splitlines()
            for j in range(len(lines)):
                m = re.search(r'(.+?>|\$) *python +([A-Za-z_0-9]+?\.py)',
                              lines[j])
                if m:
                    name = m.group(2).strip()
                    if os.path.isfile(name):
                        src_paths.add(os.path.dirname(name))
                        lines[j] = '%%run "%s"' % fullpath
                else:
                    found_unix_lines = True
            src_paths = list(src_paths)
            if src_paths and not found_unix_lines:
                # This is a sys block with run commands only
                code_blocks[i] = '\n'.join(lines)
                ipynb_code_tp[i] = 'cell'
            else:
                # Standard Markdown code
                code_blocks[i] = '\n'.join(lines)
                code_blocks[i] = indent_lines(code_blocks[i], format)
                ipynb_code_tp[i] = 'markdown'
        elif tp.endswith('hid'):
            ipynb_code_tp[i] = 'cell_hidden'
        elif tp.startswith('py'):
            ipynb_code_tp[i] = 'cell'
        else:
            # Should support other languages as well, but not for now
            code_blocks[i] = indent_lines(code_blocks[i], format)
            ipynb_code_tp[i] = 'markdown'

    # figure_files and movie_files are global variables and contain
    # all figures and movies referred to
    src_paths = list(src_paths)
    if figure_files:
        src_paths += figure_files
    if movie_files:
        src_paths += movie_files

    if src_paths:
        # Make tar file with all the source dirs with files
        # that need to be executed
        os.system('tar cfz %s %s' % (ipynb_tarfile, ' '.join(src_paths)))
        errwarn('collected all required additional files in ' + ipynb_tarfile + ' which must be distributed with the notebook')
    elif os.path.isfile(ipynb_tarfile):
        os.remove(ipynb_tarfile)


    # Parse document into markdown text, code blocks, and tex blocks.
    # Store in nested list notebook_blocks.
    notebook_blocks = [[]]
    authors = ''
    for line in filestr.splitlines():
        if line.startswith('authors = [new_author(name='):  # old author method
            authors = line[10:]
        elif _CODE_BLOCK in line:
            code_block_tp = line.split()[-1]
            if code_block_tp in ('pyhid',) or not code_block_tp.endswith('hid'):
                notebook_blocks[-1] = '\n'.join(notebook_blocks[-1]).strip()
                notebook_blocks.append(line)
            # else: hidden block to be dropped (may include more languages
            # with time in the above tuple)
        elif _MATH_BLOCK in line:
            notebook_blocks[-1] = '\n'.join(notebook_blocks[-1]).strip()
            notebook_blocks.append(line)
        else:
            if not isinstance(notebook_blocks[-1], list):
                notebook_blocks.append([])
            notebook_blocks[-1].append(line)
    if isinstance(notebook_blocks[-1], list):
        notebook_blocks[-1] = '\n'.join(notebook_blocks[-1]).strip()


    # Add block type info
    pattern = r'(\d+) +%s'
    for i in range(len(notebook_blocks)):
        if re.match(pattern % _CODE_BLOCK, notebook_blocks[i]):
            m = re.match(pattern % _CODE_BLOCK, notebook_blocks[i])
            idx = int(m.group(1))
            if ipynb_code_tp[idx] == 'cell':
                notebook_blocks[i] = ['cell', notebook_blocks[i]]
            elif ipynb_code_tp[idx] == 'cell_hidden':
                notebook_blocks[i] = ['cell_hidden', notebook_blocks[i]]
            else:
                notebook_blocks[i] = ['text', notebook_blocks[i]]
        elif re.match(pattern % _MATH_BLOCK, notebook_blocks[i]):
            notebook_blocks[i] = ['math', notebook_blocks[i]]
        else:
            notebook_blocks[i] = ['text', notebook_blocks[i]]

    # Go through tex_blocks and wrap in $$
    # (doconce.py runs align2equations so there are no align/align*
    # environments in tex blocks)
    label2tag = {}
    tag_counter = 1
    for i in range(len(tex_blocks)):
        # Extract labels and add tags
        labels = re.findall(r'label\{(.+?)\}', tex_blocks[i])
        for label in labels:
            label2tag[label] = tag_counter
            # Insert tag to get labeled equation
            tex_blocks[i] = tex_blocks[i].replace(
                'label{%s}' % label, 'label{%s} \\tag{%s}' % (label, tag_counter))
            tag_counter += 1

        # Remove \[ and \] or \begin/end{equation*} in single equations
        tex_blocks[i] = tex_blocks[i].replace(r'\[', '')
        tex_blocks[i] = tex_blocks[i].replace(r'\]', '')
        tex_blocks[i] = tex_blocks[i].replace(r'\begin{equation*}', '')
        tex_blocks[i] = tex_blocks[i].replace(r'\end{equation*}', '')
        # Check for illegal environments
        m = re.search(r'\\begin\{(.+?)\}', tex_blocks[i])
        if m:
            envir = m.group(1)
            if envir not in ('equation', 'equation*', 'align*', 'align',
                             'array'):
                errwarn("""\
*** warning: latex envir \\begin{%s} does not work well in Markdown.
    Stick to \\[ ... \\], equation, equation*, align, or align*
    environments in math environments.
""" % envir)
        eq_type = 'heading'  # or '$$'
        eq_type = '$$'
        # Markdown: add $$ on each side of the equation
        if eq_type == '$$':
            # Make sure there are no newline after equation
            tex_blocks[i] = '$$\n' + tex_blocks[i].strip() + '\n$$'
        # Here: use heading (###) and simple formula (remove newline
        # in math expressions to keep everything within a heading) as
        # the equation then looks bigger
        elif eq_type == 'heading':
            tex_blocks[i] = '### $ ' + '  '.join(tex_blocks[i].splitlines()) + ' $'

        # Add labels for the eqs above the block (for reference)
        if labels:
            #label_tp = '<a name="%s"></a>'
            label_tp = '<div id="%s"></div>'
            tex_blocks[i] = '<!-- Equation labels as ordinary links -->\n' + \
                            ' '.join([label_tp % label
                                      for label in labels]) + '\n\n' + \
                                      tex_blocks[i]

    # blocks is now a list of text chunks in markdown and math/code line
    # instructions. Insert code and tex blocks
    for i in range(len(notebook_blocks)):
        if _CODE_BLOCK in notebook_blocks[i][1] or _MATH_BLOCK in notebook_blocks[i][1]:
            words = notebook_blocks[i][1].split()
            # start of notebook_blocks[i]: number block-indicator code-type
            n = int(words[0])
            if _CODE_BLOCK in notebook_blocks[i][1]:
                notebook_blocks[i][1] = code_blocks[n]  # can be list!
            if _MATH_BLOCK in notebook_blocks[i][1]:
                notebook_blocks[i][1] = tex_blocks[n]

    # Make IPython structures

    nb_version = int(option('ipynb_version=', '4'))
    if nb_version == 3:
        try:
            from IPython.nbformat.v3 import (
                new_code_cell, new_text_cell, new_worksheet,
                new_notebook, new_metadata, new_author)
            nb = new_worksheet()
        except ImportError:
            errwarn('*** error: could not import IPython.nbformat.v3!')
            errwarn('    set --ipynb_version=4 or leave out --ipynb_version=3')
            _abort()
    elif nb_version == 4:
        try:
            from nbformat.v4 import (
                new_code_cell, new_markdown_cell, new_notebook)
        except ImportError:
            # Try old style
            try:
                from IPython.nbformat.v4 import (
                    new_code_cell, new_markdown_cell, new_notebook)
            except ImportError:
                errwarn('*** error: cannot do import nbformat.v4 or IPython.nbformat.v4')
                errwarn('    make sure IPython notebook or Jupyter is installed correctly')
                _abort()
        cells = []

    mdstr = []  # plain md format of the notebook
    prompt_number = 1
    for block_tp, block in notebook_blocks:
        if (block_tp == 'text' or block_tp == 'math') and block != '':
            # Pure comments between math/code and math/code come
            # out as empty blocks, should detect that situation
            # (challenging - can have multiple lines of comments,
            # or begin and end comment lines with important things between)
            if nb_version == 3:
                nb.cells.append(new_text_cell(u'markdown', source=block))
            elif nb_version == 4:
                cells.append(new_markdown_cell(source=block))
            mdstr.append(('markdown', block))
        elif block_tp == 'cell' and block != '' and block != []:
            if isinstance(block, list):
                for block_ in block:
                    block_ = block_.rstrip()
                    if block_ != '':
                        if nb_version == 3:
                            nb.cells.append(new_code_cell(
                                input=block_,
                                prompt_number=prompt_number,
                                collapsed=False))
                        elif nb_version == 4:
                            cells.append(new_code_cell(
                                source=block_,
                                execution_count=prompt_number,
                                metadata=dict(collapsed=False)))
                        prompt_number += 1
                        mdstr.append(('codecell', block_))
            else:
                block = block.rstrip()
                if block != '':
                    if nb_version == 3:
                        nb.cells.append(new_code_cell(
                            input=block,
                            prompt_number=prompt_number,
                            collapsed=False))
                    elif nb_version == 4:
                        cells.append(new_code_cell(
                            source=block,
                            execution_count=prompt_number,
                            metadata=dict(collapsed=False)))
                    prompt_number += 1
                    mdstr.append(('codecell', block))
        elif block_tp == 'cell_hidden' and block != '':
            block = block.rstrip()
            if nb_version == 3:
                nb.cells.append(new_code_cell(
                    input=block, prompt_number=prompt_number, collapsed=True))
            elif nb_version == 4:
                cells.append(new_code_cell(
                    source=block,
                    execution_count=prompt_number,
                    metadata=dict(collapsed=True)))
            prompt_number += 1
            mdstr.append(('codecell', block))

    """
    # Dump the notebook cells in a simple ASCII format
    # (doc/src/ipynb/ipynb_generator.py can translate it back to .ipynb file)
    f = open(dofile_basename + '.md-ipynb', 'w')
    for cell_tp, block in mdstr:
        if cell_tp == 'markdown':
            f.write('\n-----\n\n')
        elif cell_tp == 'codecell':
            f.write('\n-----py\n\n')
        f.write(block)
    f.close()
    """

    if nb_version == 3:
        # Catch the title as the first heading
        m = re.search(r'^#+\s*(.+)$', filestr, flags=re.MULTILINE)
        title = m.group(1).strip() if m else ''
        # md below is not used for anything
        if authors:
            authors = eval(authors)
            md = new_metadata(name=title, authors=authors)
        else:
            md = new_metadata(name=title)
        nb = new_notebook(worksheets=[nb], metadata=new_metadata())
        # Let us make v4 notebook here by upgrading
        from IPython.nbformat.v4 import upgrade
        nb = upgrade(nb)
        import IPython.nbformat.v4.nbjson as nbjson

        # Convert nb to json format
        filestr = nbjson.writes(nb)
    elif nb_version == 4:
        nb = new_notebook(cells=cells)
        from IPython.nbformat import writes
        filestr = writes(nb, version=4)

    # Check that there are no empty cells:
    if '"input": []' in filestr:
        errwarn('*** error: empty cells in notebook - report bug in DocOnce')
        _abort()
    # must do the replacements here at the very end when json is written out
    # \eqref and labels will not work, but labels (only in math) do no harm
    filestr = re.sub(r'([^\\])label\{', r'\g<1>\\\\label{', filestr,
                     flags=re.MULTILINE)
    # \\eqref{} just gives (???) link at this stage - future versions
    # will probably support labels
    #filestr = re.sub(r'\(ref\{(.+?)\}\)', r'\\eqref{\g<1>}', filestr)
    # Now we use explicit references to tags
    def subst(m):
        label = m.group(1)
        try:
            return r'[(%s)](#%s)' % (label2tag[label], label)
        except KeyError as e:
            errwarn('*** error: label "%s" is not defined' % str(e))

    filestr = re.sub(r'\(ref\{(.+?)\}\)', subst, filestr)
    """
    # MathJax reference to tag (recall that the equations have both label
    # and tag (know that tag only works well in HTML, but this mjx-eqn-no
    # label does not work in ipynb)
    filestr = re.sub(r'\(ref\{(.+?)\}\)',
                     lambda m: r'[(%s)](#mjx-eqn-%s)' % (label2tag[m.group(1)], label2tag[m.group(1)]), filestr)
    """
    #filestr = re.sub(r'\(ref\{(.+?)\}\)', r'Eq (\g<1>)', filestr)

    '''
    # Final fixes: replace all text between cells by markdown code cells
    # Note: the patterns are overlapping so a plain re.sub will not work,
    # here we run through all blocks found and subsitute the first remaining
    # one, one by one.
    pattern = r'   \},\n(.+?)\{\n    "cell_type":'
    begin_pattern = r'^(.+?)\{\n    "cell_type":'
    remaining_block_begin = re.findall(begin_pattern, filestr, flags=re.DOTALL)
    remaining_blocks = re.findall(pattern, filestr, flags=re.DOTALL)
    import string
    for block in remaining_block_begin + remaining_blocks:
        filestr = string.replace(filestr, block, json_markdown(block) + '   ',
                                 maxreplace=1)
    filestr_end = re.sub(r'   \{\n    "cell_type": .+?\n   \},\n', '', filestr,
                         flags=re.DOTALL)
    filestr = filestr.replace(filestr_end, json_markdown(filestr_end))
    filestr = """{
 "metadata": {
  "name": "SOME NAME"
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
""" + filestr.rstrip() + '\n'+ \
    json_pycode('', final_prompt_no+1, 'python').rstrip()[:-1] + """
   ],
   "metadata": {}
  }
 ]
}"""
    '''
    return filestr
Exemple #35
0
def ipynb_figure(m):
    # m.group() must be called before m.group('name')
    text = '<!-- dom:%s -->\n<!-- begin figure -->\n' % m.group()

    filename = m.group('filename')
    caption = m.group('caption').strip()
    opts = m.group('options').strip()
    if opts:
        info = [s.split('=') for s in opts.split()]
        opts = ' ' .join(['%s=%s' % (opt, value)
                          for opt, value in info
                          if opt not in ['frac', 'sidecap']])

    global figure_files
    if not filename.startswith('http'):
        figure_files.append(filename)

    # Extract optional label in caption
    label = None
    pattern = r' *label\{(.+?)\}'
    m = re.search(pattern, caption)
    if m:
        label = m.group(1).strip()
        caption = re.sub(pattern, '', caption)

    display_method = option('ipynb_figure=', 'imgtag')
    if display_method == 'md':
        # Markdown image syntax for embedded image in text
        # (no control of size, then one must use HTML syntax)
        if label is not None:
            #text += '<a name="%s"></a>\n' % label
            text += '<div id="%s"></div>\n' % label
        text += '![%s](%s)' % (caption, filename)
    elif display_method == 'imgtag':
        # Plain <img tag, allows specifying the image size
        if label is not None:
            #text += '<a name="%s"></a>' % label
            text += '<div id="%s"></div>\n' % label
        # Fix caption markup so it becomes html
        from doconce import INLINE_TAGS_SUBST, INLINE_TAGS
        for tag in 'bold', 'emphasize', 'verbatim':
            caption = re.sub(INLINE_TAGS[tag], INLINE_TAGS_SUBST['html'][tag],
                             caption, flags=re.MULTILINE)
        text += """
<p>%s</p>
<img src="%s" %s>

""" % (caption, filename, opts)
    elif display_method == 'Image':
        # Image object
        # NOTE: This code will normally not work because it inserts a verbatim
        # block in the file *after* all such blocks have been removed and
        # numbered. doconce.py makes a test prior to removal of blocks and
        # runs the handle_figures and movie substitution if ipynb format
        # and Image or movie object display.
        text += '\n'
        if label is not None:
            text += '<div id="%s"></div>' % label
        text += '<!-- options: %s -->\n' % opts
        text = '!bc pycod\n'
        global figure_encountered
        if not figure_encountered:
            # First time we have a figure, we must import Image
            text += 'from IPython.display import Image\n'
            figure_encountered = True
        if caption:
            text += '# ' + caption
        if filename.startswith('http'):
            keyword = 'url'
        else:
            keyword = 'filename'
        text += 'Image(%s="%s")\n' % (keyword, filename)
        text += '!ec\n'
    else:
        errwarn('*** error: --ipynb_figure=%s is illegal, must be md, imgtag or Image' % display_method)
        _abort()
    text += '<!-- end figure -->\n'
    return text
Exemple #36
0
def sphinx_quiz_runestone(quiz):
    quiz_feedback = option("quiz_explanations=", "on")

    text = ""
    if "new page" in quiz:
        text += ".. !split\n%s\n%s" % (quiz["new page"], "-" * len(quiz["new page"]))

    text += ".. begin quiz\n\n"
    global question_counter
    question_counter += 1
    # Multiple correct answers?
    if sum([1 for choice in quiz["choices"] if choice[0] == "right"]) > 1:
        text += ".. mchoicema:: question_%d" % question_counter + "\n"
    else:
        text += ".. mchoicemf:: question_%d" % question_counter + "\n"

    def fix_text(s, tp="answer"):
        """
        Answers and feedback in RunestoneInteractive book quizzes
        cannot contain math, figure and rst markup. Perform fixes.
        """
        drop = False
        if "math::" in s:
            errwarn("\n*** warning: quiz %s with math block not supported:" % tp)
            errwarn(s)
            drop = True
        if ".. code-block::" in s:
            errwarn("\n*** warning: quiz %s with code block not supported:" % tp)
            errwarn(s)
            drop = True
        if ".. figure::" in s:
            errwarn("\n*** warning: quiz %s with figure not supported:" % tp)
            errwarn(s)
            drop = True
        if drop:
            return ""
        # Make multi-line paragraph a one-liner
        s = " ".join(s.splitlines()).rstrip()
        # Fixes
        pattern = r"`(.+?) (<https?.+?)>`__"  # URL
        s = re.sub(pattern, '<a href="\g<2>"> \g<1> </a>', s)
        pattern = r"``(.+?)``"  # verbatim
        s = re.sub(pattern, "<tt>\g<1></tt>", s)
        pattern = r":math:`(.+?)`"  # inline math
        s = re.sub(pattern, "<em>\g<1></em>", s)  # mimic italic....
        pattern = r":\*(.+?)\*"  # emphasize
        s = re.sub(pattern, "\g<1>", s, flags=re.DOTALL)
        return s

    import string

    correct = []
    for i, choice in enumerate(quiz["choices"]):
        if i > 4:  # not supported
            errwarn("*** warning: quiz with %d choices gets truncated (first 5)" % len(quiz["choices"]))
            break
        letter = string.ascii_lowercase[i]
        text += "   :answer_%s: " % letter
        answer = fix_text(choice[1], tp="answer")
        if not answer:
            answer = "Too advanced typesetting prevents the text from being rendered"
        text += answer + "\n"
        if choice[0] == "right":
            correct.append(letter)
    if correct:
        text += "   :correct: " + ", ".join(correct) + "\n"
    else:
        errwarn("*** error: correct choice in quiz has index > 5 (max 5 allowed for RunestoneInteractive books)")
        errwarn(quiz["question"])
        _abort()
    for i, choice in enumerate(quiz["choices"]):
        if i > 4:  # not supported
            break
        letter = string.ascii_lowercase[i]
        text += "   :feedback_%s: " % letter  # must be present
        if len(choice) == 3 and quiz_feedback == "on":
            feedback = fix_text(choice[2], tp="explanation")
            if not feedback:
                feedback = "(Too advanced typesetting prevents the text from being rendered)"
            text += feedback
        text += "\n"

    text += "\n" + indent_lines(quiz["question"], "sphinx", " " * 3) + "\n\n\n"
    return text