#!/usr/bin/env python #-*- coding: utf-8 -*- # Copyright (c) 2007 Jens Kadenbach # 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, Web Link [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()