#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2007 Jens Kadenbach <
[email protected]>
# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What The Fuck You Want
# To Public License, Version 2, as published by Sam Hocevar. See
#
http://sam.zoy.org/wtfpl/COPYING for more details.
"""\
Renumbers and reorders footnotes in a text file.
Task description available here:
http://www.linux-magazin.de/content/download/27388/238793/file/Tasks_languages.pdf
"""
from __future__ import with_statement
import sys
import re
from cStringIO import StringIO
from optparse import OptionParser
from itertools import takewhile, count
from functools import partial
SAMPLE = StringIO('''A great brown fox [13] jumped of a pile of lorem ipsum [4],
[7]. He met with a silver penguin, browsing the Linux Kernel
Mailinglist [3]. They debated whether to start a C-program with
"main (int argc, char **argv)" or with "main (int argc, char *argv[])".
Square brackets annoyed them [9999]. Multiple references exist [4].
@footnote:
[13] Al Fabetus: "On characters and animals", 1888, self published.
[4] Lorem Ipsum, <a href="
http://link.org/Lorem_Ipsum">Web
Link</a>
[9999] Annoying Link.
[7] B. Fox: "More on Blind Text".
[3] Linux Kernel Maintainers: LKML
''')
FOOTSPLIT = '@footnote:\n'
NUMBER = re.compile(r'\[(\d+)\]')
def _parse_args():
parser = OptionParser(usage='%prog [options] FILE', description=__doc__)
parser.add_option('-t', '--test',
help='use test file', action="store_true", dest='test')
parser.add_option('-a', '--reorder', action="store_true", dest='foot',
help='order footnotes in order of appearance in text')
parser.set_defaults(test=False, foot=False)
opts, args = parser.parse_args()
if not len(args) == 1 or opts.test:
parser.error("File needed!")
return opts, args[0]
def splitfooter(line):
""" [3] Linux Kernel Maintainers: LKML
- > "3"
"""
return line[1:].split(']', 1)
def as_num(line):
""" key-Function for sorting by
int-Value of the footer-line
"""
try:
start, _ = splitfooter(line)
return int(start)
except ValueError:
# bad line, ignore it
return None
def create_footer(iterable, order=None):
""" create and order the footer if
an order is given, else create
an order and return footer and order
"""
if not order:
notes = {}
c = count(1)
def store(num):
idx = c.next()
notes[num] = idx
return idx
else:
def store(num):
return order[num]
footer = []
for line in iterable:
try:
start, content = splitfooter(line)
num = int(start)
except ValueError:
# concat lines of one footer
try:
footer[-1] += line
except IndexError:
# happens maybe on first line
footer.append(line)
else:
idx = store(num)
footer.append("[%d]%s" % (idx, content))
if not order:
# back to start
return notes, footer
else:
return footer
def make_replacer(notes=None):
""" Creates the regexp-replacer function
for the body. Gets a footnotes-cache
or returns creates one
"""
def get_val(match): return int(match.group(1))
if notes:
def replace_num(match):
""" replace found value
"""
n = get_val(match)
# 0 when not found
return "[%d]" % notes.get(n, 0)
return replace_num
else:
notes = {}
c = count(1)
def replace_num(match):
""" return new index and assign
it to current index in notes
"""
n = get_val(match)
if not n in notes:
i = c.next()
notes[n] = i
else:
i = notes[n]
return "[%d]" % i
return replace_num, notes
# iterator that ends when it encounters FOOTSPLIT
def _end(l) : return not l == FOOTSPLIT
extract_body = partial(takewhile, _end)
def sort_footnotes(fh, sort_footer):
""" sort_footer: order footnotes by appearance
in the body
"""
if not sort_footer:
# place cursor at the beginning of footer
for line in extract_body(fh):
pass
notes, footer = create_footer(fh)
replace_num = make_replacer(notes)
# place cursor at start
fh.seek(0)
body = extract_body(fh)
else:
body, foot = extract_body(fh), fh
replace_num, notes = make_replacer()
for line in body:
yield NUMBER.sub(replace_num, line)
if sort_footer:
footer = sorted(create_footer(foot, order=notes), key=as_num)
yield FOOTSPLIT
for line in footer:
yield line
def main():
options, input = _parse_args()
try:
fh = SAMPLE if options.test else open(input, 'r')
sys.stdout.writelines(sort_footnotes(fh, options.foot))
finally:
fh.close()
if __name__ == '__main__':
main()