POD is a markup format that is used by Perl. It's sometimes found embedded within the code. You can find more information on it from the official docs on perlpod, and perlpodspec.

I'm wondering if there is a simple method to delete it.

I think something like that removes all lines that start with /^=(?:head\d|pod|item)/ ... and end with /^=cut/ would work reasonably well, as most of it looks like this

=item snazzle($)

The snazzle() function will behave in the most spectacular
form that you can possibly imagine, not even excepting
cybernetic pyrotechnics.

=cut back to the compiler, nuff of this pod stuff!

sub snazzle($) {
    my $thingie = shift;
  • Could you provide an example of file containing some POD you'd like to remove please? For example, I am not familiar at all with it, and seeing an example of what you have and want to achieve would help providing you a better quality answer. – padawin Apr 12 '19 at 13:54
  • @padawin see the update, it's pretty useless to show you a small example of a markup language. It's like asking for an example of HTML. It's a format with a lot of complexities, but nontheless I tried to show a snippet and provide useful links above. – I Support The Boycott Apr 12 '19 at 14:04
  • 1
    Fair enough. Does it always start with =something and ends with =cut ? – padawin Apr 12 '19 at 14:06
  • I think so. I'm not the expert of this format either, I just hate seeing it inside my vim and triggering searches and the like. – I Support The Boycott Apr 12 '19 at 14:09

Just use an ex command with searches for ranges:

:/^=[^\s]/,/^=cut/ delete

Repeat with @:. Or, for the whole file,

:g/^=[^\s]/,/^=cut/ delete

You can make it a hotkey with dp

:nnoremap <leader>dp :g/^=[^\s]/,/^=cut/ delete<cr>
  • That's awesome!!! THANKS. – I Support The Boycott Apr 12 '19 at 18:08
  • I'm not sure the ex command is useful for me, what is useful is the nnoremap <leader>dp – I Support The Boycott Apr 12 '19 at 18:10
  • @EvanCarroll you can easily create a normal mode mapping to do the above – D. Ben Knoble Apr 12 '19 at 18:15
  • 1
    Ok, I had no idea range search existed, that's brilliant – padawin Apr 12 '19 at 19:20

You can do the following mappings:

" *D*elete *P*od
nnoremap <leader>dp gg/^=[^\s]<cr>V/^=cut<cr>d
" *F*old *P*od
nnoremap <leader>fp /^=[^\s]<cr>V/^=cut<cr>jzf

To either delete the first POD of the file, or fold the next POD accessible from your cursor (if you are in a POD, only part of it will be folded)

  • That's only deleting the first pod block, there could be hundreds of them. – I Support The Boycott Apr 12 '19 at 14:19
  • Is there a way to make that all <leader>dp? rather than a three step process? – I Support The Boycott Apr 12 '19 at 14:22
  • 1
    Maybe, you'd have to store the count somewhere, but sadly, I don't know how. – padawin Apr 12 '19 at 14:22
  • the count works, but the number<leader>dp does not work – I Support The Boycott Apr 12 '19 at 14:24
  • 1
    hmmm indeed. You can store it in a macro, it works this way: qq<leader>dpq then number-1@q – padawin Apr 12 '19 at 14:26

So, I wrote a Python script foldpod (at bottom) that can be used to "fold" POD into a magical perl comment that's a really long base64-encoded line.

I have made no attempt to parse the full POD format, it recognizes =pod, =begin, =item, and =head1 as beginning a POD block and recognizes =cut as ending a block. Starting a block multiple times within the same block is not an error from the perspective of foldpod.

It does not bother to check whether the lines surrounding a POD directive are blank or not, so it should work acceptably well even if the POD documentation of the perl source file is malformed.

Unterminated blocks are not an error and get passed through as is.

When invoked as foldpod -u it will unfold the magical base-64 encoded comments.

As long as you have set nowrap enabled, this should make the POD comments substantially less annoying.

If you don't use set nowrap it should still be possible to target lines containing MAGIC_POD_COMMENT with a fold expression or conceal them in some way.

The result looks like this with no wrapping:



package main;
use 5.8.7;
use strict;
use warnings;
use IO::File;
use Getopt::Long ();    #   Resist name-space pollution!
use Pod::Usage ();      #   Ditto!

And here's the script.

#! /usr/bin/env python3

import argparse
import sys
from io import StringIO
from base64 import b64encode, b64decode
import unittest
import re

from shutil import copyfile

isa = isinstance

parser = argparse.ArgumentParser(prog="foldpod")
    "-S", "--selftest", action="store_true", dest="selftest", help="run test suite",
    help="unfold flattened POD comments",
args, files = parser.parse_known_args()
args_ = vars(args)
del args
args = args_

POD_START = re.compile(r"^=pod\b|^=begin\b|^=head1\b|^=item\b")

def pod_start(line):
    return bool(POD_START.match(line))

def pod_end(line):
    return line.rstrip() == "=cut"

def magic_comment(line):
    words = line.split()
    if len(words) < 2:
        return False
    if words[0] == "#" and words[1] == "MAGIC_POD_COMMENT":
        return True
    return False

def pod_classify(line):
    >>> pod_classify('')
    >>> pod_classify('=pod')
    >>> pod_classify('=cut')
    is_start = pod_start(line)
    is_end = pod_end(line)
    is_magic_comment = magic_comment(line)
    hits = sum([is_start, is_end, is_magic_comment])
    if hits == 0:
        return None
    elif hits == 1:
        if is_start:
            return "START"
        elif is_end:
            return "END"
        elif is_magic_comment:
            return "MAGIC_POD_COMMENT"
            raise RuntimeError("couldn't handle line type")
        raise ValueError("line matched more than one pattern: %s" % line)

def fold_pod(itr):
    returns: iterator of lines of file with pod sections
    organized into base64-encoded magical comments.
    Note that the lines emitted by this function always have
    a newline terminator.

    >>> list(fold_pod(['a', 'b']))
    ['a\n', 'b\n']
    if isinstance(itr, (str, bytes)):
        raise ValueError("expecting iterator of lines, not single line")
    cur_pod = None
    for line in itr:
        line = line if line.endswith("\n") else line + "\n"
        if None is cur_pod:
            klass = pod_classify(line)
            # if we're a start line, start filling pod buffer
            if klass == "START":
                cur_pod = StringIO()
            # otherwise just emit the line
                yield line
            klass = pod_classify(line)
            if klass == "END":
                b64 = b64encode(cur_pod.getvalue().encode("utf-8"))
                b64 = b64.decode("utf-8")
                assert isa(b64, str)
                content = "# MAGIC_POD_COMMENT : %s\n" % b64
                cur_pod = None
                yield content
    # once we're out of the iterator,
    # read from an incomplete cur_pod buffer a line at a time
    if cur_pod:
        for line in cur_pod.getvalue().splitlines(True):
            yield line

def fold_pod_(*args, **kwargs):
    return list(fold_pod(*args, **kwargs))

def unfold_pod(itr):
    if isinstance(itr, (str, bytes)):
        raise ValueError("expecting iterator of lines, not single line")
    for line in itr:
        if pod_classify(line) == "MAGIC_POD_COMMENT":
            words = line.split()
            assert len(words) == 4
            assert words[0] == "#"
            assert words[1] == "MAGIC_POD_COMMENT"
            assert words[2] == ":"
            b64 = words[3]
            b64 = b64.encode("utf-8")
            out = b64decode(b64)
            out = out.decode("utf-8")
            for l in out.splitlines(True):
                yield l
            yield line

def unfold_pod_(*args, **kwargs):
    return list(unfold_pod(*args, **kwargs))

class TestFoldPod(unittest.TestCase):
    def test_fold_pod_empty(self):
        self.assertEqual(fold_pod_([]), [])

    def test_fold_single(self):
        self.assertEqual(fold_pod_(["a"]), ["a\n"])

    def test_fold_just_begin(self):
        self.assertEqual(fold_pod_(["=begin"]), ["=begin\n"])

    def test_fold_empty_pod(self):
        xs = fold_pod_(["=begin", "=cut"])
        self.assertEqual(len(xs), 1)
        first = xs[0]
        words = first.split()
        self.assertEqual(words[0], "#")
        self.assertEqual(words[1], "MAGIC_POD_COMMENT")

    def test_fold_unfold_round_trip(self):
        xs = ["=begin\n", "aaaaa\n", "=cut\n"]
        rt = unfold_pod_(fold_pod_(xs))
        self.assertEqual(rt, xs)

def main(files, unfold=False, selftest=False):
    if selftest:
        return do_selftest(files=files)
    if files:
        for path in files:
            bak = path + ".bak"
            copyfile(src=path, dst=bak)
            filt = unfold_pod if unfold else fold_pod
            with open(bak) as source:
                with open(path, "w") as sink:
                    for line in filt(source):
        filt = unfold_pod if unfold else fold_pod
        source = sys.stdin
        for line in filt(source):

def do_selftest(files):
    import doctest

    argv = ["foldpod"] + files

if __name__ == "__main__":
    selftest = args["selftest"]
    unfold = args["unfold"]
    main(files=files, selftest=selftest, unfold=unfold)
  • What is the vars function you call on args, and why does it require the temp/delete/assign rigamarole? – D. Ben Knoble Apr 13 '19 at 4:08
  • @D.BenKnoble ... technically not all of that is necessary. The vars method produces a dictionary from the result of parse_args. The only really necessary bit of plumbing I do need is extract the unparsed arguments into files and reassemble a fake argv to hand to unittest.main so that function doesn't try to parse the -selftest flag itself. If it's distracting I can simplify the argument management. – Gregory Nisbet Apr 13 '19 at 4:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.