0

POD is a markup file that is used by Perl. It's a pretty unreasonable documentation format that has nothing to do with perl (accept for being the only known use-case), and it's sometimes found intermingled with code. Think MarkDown-nroff hybrid, but optimized to distract the user from anything they may be doing. 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 simply delete it until I find out how to better work with 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 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. – Evan Carroll Apr 12 at 14:04
  • 1
    Fair enough. Does it always start with =something and ends with =cut ? – padawin Apr 12 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. – Evan Carroll Apr 12 at 14:09
3

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. – Evan Carroll Apr 12 at 18:08
  • I'm not sure the ex command is useful for me, what is useful is the nnoremap <leader>dp – Evan Carroll Apr 12 at 18:10
  • @EvanCarroll you can easily create a normal mode mapping to do the above – D. Ben Knoble Apr 12 at 18:15
  • 1
    Ok, I had no idea range search existed, that's brilliant – padawin Apr 12 at 19:20
2

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. – Evan Carroll Apr 12 at 14:19
  • Is there a way to make that all <leader>dp? rather than a three step process? – Evan Carroll Apr 12 at 14:22
  • 1
    Maybe, you'd have to store the count somewhere, but sadly, I don't know how. – padawin Apr 12 at 14:22
  • the count works, but the number<leader>dp does not work – Evan Carroll Apr 12 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 at 14:26
0

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:

#!/usr/local/bin/perl

# MAGIC_POD_COMMENT : PWhlYWQxIE5BTUUKCnBhcnNlUmVmU2VxR2Vub21lcy5wbCAtIHBhcnNlIFJlZlNlcSBnZW5vbWVzIGZyb20gTkNCSQoKPWhlYWQxIFNZTk9QU0lTCgp
Vc2U6CgogICAgcGVybCBwYXJzZVJlZlNlcUdlbm9tZXMucGwgWy0taGVscF0gWy0tbWFuXQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgWy0tY2xlYXJEYl0gWy0td3Jp
dGVUb0RiXQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLS1wYXRoIHBhdGggLS1kaXZpc2lvbiBuYW1lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtLW91d
CBuYW1lCgpFeGFtcGxlczoKCiAgICBwZXJsIHBhcnNlUmVmU2VxR2Vub21lcy5wbCAtLWhlbHAKCiAgICBwZXJsIHBhcnNlUmVmU2VxR2Vub21lcy5wbCAtLW1hbgoKICAgICMgIC
BMb2FkIGFsbCBSZWZTZXEgZGF0YToKCiAgICBwZXJsIHBhcnNlUmVmU2VxR2Vub21lcy5wbCAtLWNsZWFyRGIgLS13cml0ZVRvRGIKICAgICAgICAgICAgICAgICAgICAgICAgICA
gICAgIC0tcGF0aCAvZGF0YS9nZW5vbWVzIC0tZGl2aXNpb24gYmN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtLW91dCBiY3QuZ2Vub21lcy5wZXAuZmFzdGEKCj1o
ZWFkMSBERVNDUklQVElPTgoKVGhpcyBzY3JpcHQgaXMgcGFydCBvZiB0aGUgZ2Vub21lIGFubm90YXRpb24gcGlwZWxpbmUuIFRoZSBzY3JpcHQgc2NhbnMKdGhlIEdlbkJhbmstZ
m9ybWF0IFJlZlNlcSBnZW5vbWVzIGZyb20gTkNCSSBhbmQgaW5zZXJ0cyB0aGUgZGF0YSBpbnRvIHRoZQpNeVNRTCBkYXRhYmFzZTsgb3B0aW9uYWxseSwgdGhlIHNjcmlwdCBjcm
VhdGVzIGEgRmFzdGEtZm9ybWF0IG91dHB1dCBmaWxlCmNvbnRhaW5pbmcgdGhlIHByb3RlaW4gc2VxdWVuY2VzLgoKPWhlYWQxIEFSR1VNRU5UUwoKcGFyc2VSZWZTZXFHZW5vbWV
zLnBsIHRha2VzIHRoZSBmb2xsb3dpbmcgYXJndW1lbnRzOgoKPW92ZXIgNAoKPWl0ZW0gaGVscAoKICAtLWhlbHAKCihPcHRpb25hbC4pIERpc3BsYXlzIHRoZSB1c2FnZSBtZXNz
YWdlLgoKPWl0ZW0gbWFuCgogIC0tbWFuCgooT3B0aW9uYWwuKSBEaXNwbGF5cyBhbGwgZG9jdW1lbnRhdGlvbi4KCj1pdGVtIHBhdGgKCiAgLS1wYXRoIG5hbWUKCihSZXF1aXJlZ
C4pIFNldHMgdGhlIHBhdGggdG8gdGhlIGRpcmVjdG9yeSBpbiB3aGljaCB0aGUgZGl2aXNpb24gaXMgZm91bmQuCgo9aXRlbSBkaXZpc2lvbgoKICAtLWRpdmlzaW9uIG5hbWUKCi
hSZXF1aXJlZC4pIFNldHMgdGhlIG5hbWUgb2YgdGhlIEdlbkJhbmsgZGl2aXNpb24sIGEgc3ViZGlyZWN0b3J5IG9mIHRoZSBwYXRoCmNvbnRhaW5pbmcgb25lIG9yIG1vcmUgR2V
uQmFuayBmbGF0IGZpbGVzLgoKPW92ZXIgOAoKPWl0ZW0gYmN0CgpUaGUgUmVmU2VxIG1pY3JvYmlhbCBnZW5vbWVzIGZyb20gTkNCSS4KCj1iYWNrCgo9aXRlbSB3cml0ZVRvRGIK
CiAgLS13cml0ZVRvRGIKCihPcHRpb25hbC4pIEluY2x1ZGUgdGhpcyBhcmd1bWVudCBpZiB5b3Ugd2FudCB0byB3cml0ZSBkYXRhIHRvIHRoZSBkYXRhYmFzZS4KCj1pdGVtIGNsZ
WFyRGIKCiAgLS1jbGVhckRiCgooT3B0aW9uYWwuKSBJbmNsdWRlIHRoaXMgYXJndW1lbnQgaWYgeW91IHdhbnQgdG8gZGVsZXRlIGFsbCBkYXRhIGZyb20gYWxsCmRhdGFiYXNlIH
RhYmxlcyBiZWZvcmUgcGFyc2luZyBhbmQgaW5zZXJ0aW5nIGJlZ2lucy4KCj1pdGVtIG91dEZpbGVOYW1lCgogIC0tb3V0RmlsZU5hbWUgbmFtZQoKKE9wdGlvbmFsLikgVGhlIG5
hbWUgb2YgdGhlIGZhc3RhLWZvcm1hdCBvdXRwdXQgZmlsZSBjb250YWluaW5nIHRoZSBwcm90ZWluCnNlcXVlbmNlcyBleHRyYWN0ZWQgZnJvbSB0aGUgQ0RTIGZlYXR1cmVzIG9m
IGVhY2ggYWNjZXNzaW9uLiBJZiBub3Qgc3BlY2lmaWVkLApubyBmYXN0YSBkYXRhIHdpbGwgYmUgY3JlYXRlZC4KCj1iYWNrCgo9aGVhZDEgQVVUSE9SCgpDb25yYWQgSGFsbGluZ
ywgRTxsdD5jb25yYWQuaGFsbGluZ0BzcGhhZXJ1bGEuY29tRTxndD4uCgo9aGVhZDEgQ09QWVJJR0hUCgpUaGlzIHByb2dyYW0gaXMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIEFydG
lzdGljIExpY2Vuc2UuCgo9aGVhZDEgREFURQoKMzEtTWFyLTIwMDYKCj1jdXQK


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")
parser.add_argument(
    "-S", "--selftest", action="store_true", dest="selftest", help="run test suite",
)
parser.add_argument(
    "-u",
    "--unfold",
    action="store_true",
    dest="unfold",
    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):
    r"""
    >>> pod_classify('')
    >>> pod_classify('=pod')
    'START'
    >>> pod_classify('=cut')
    'END'
    """
    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"
        else:
            raise RuntimeError("couldn't handle line type")
    else:
        raise ValueError("line matched more than one pattern: %s" % line)


def fold_pod(itr):
    r"""
    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()
                cur_pod.write(line)
            # otherwise just emit the line
            else:
                yield line
        else:
            klass = pod_classify(line)
            if klass == "END":
                cur_pod.write(line)
                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.close()
                cur_pod = None
                yield content
            else:
                cur_pod.write(line)
    # 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
        cur_pod.close()


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
        else:
            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):
                        sink.write(line)
    else:
        filt = unfold_pod if unfold else fold_pod
        source = sys.stdin
        for line in filt(source):
            sys.stdout.write(line)


def do_selftest(files):
    import doctest

    doctest.testmod()
    argv = ["foldpod"] + files
    unittest.main(argv=argv)


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 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 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.