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)
=something
and ends with=cut
?