#!/usr/bin/python
#
# footnotes2.py
#
"""Sort references and footnotes of a file.

The general usage is:

  programname [-a] <FILE>

where <FILE> contains a number of lines with references marked as
'[<NUM>]', a following marker '@footnotes:' and a footnote section
where all footnotes referenced in the text are written.

The program then sorts the footnotes in numerical order (without `-a`
option) or (with `-a` given) renumbers the references in ascending
order and renumbers the footnotes app

The output goes to stdout.

Without a filename we get a usage hint::

  >>> main(argv=['progname'])
  Usage: progname [-a] <filepath>

An example file could look like this::

  >>> mycontent = '''
  ... A text with a footnote[1] does not contain as much wisdom as
  ... as a text without it[13]. Instead it has footnotes to propagate
  ... its wisdom[2].
  ... @footnotes:
  ... [1] Wisdom and other things to dispair.
  ... [13] Some more of funny things one can do.
  ... [2] Unwise Things You Will Like to Do.
  ... '''

We now feed this content to our main function::

  >>> open('testfile', 'w').write(mycontent)
  >>> main(argv=['progname', 'testfile'])
  <BLANKLINE>
  A text with a footnote[1] does not contain as much wisdom as
  as a text without it[13]. Instead it has footnotes to propagate
  its wisdom[2].
  <BLANKLINE>
  @footnotes:
  <BLANKLINE>
  [1] Wisdom and other things to dispair.
  [2] Unwise Things You Will Like to Do.
  [13] Some more of funny things one can do.

As we can see, the footnote list was sorted. Now we retry, but this
time we also reorder the references::

  >>> main(argv=['progname', '-a', 'testfile'])
  <BLANKLINE>
  A text with a footnote[1] does not contain as much wisdom as
  as a text without it[2]. Instead it has footnotes to propagate
  its wisdom[3].
  <BLANKLINE>
  @footnotes:
  <BLANKLINE>
  [1] Wisdom and other things to dispair.
  [2] Some more of funny things one can do.
  [3] Unwise Things You Will Like to Do.

"""
import re, sys

def get_ref_map(alist):
    """Turn a list into a map.

    The values of a certain key is the order number the value appears
    in the incoming list::

      >>> mylist = [13,12,12,11,13]
      >>> get_ref_map(mylist)
      {11: 3, 12: 2, 13: 1}

    Here every value of the input list appears once as a key and has
    the order number of its occurence in the list as mapped value.

    The number 11 is the 3rd unique number that appears in the list,
    so it maps to the numer 3.
    
    """
    b = dict()
    c = 1
    for x in alist:
        # Using get() here is much faster than `in`
        if b.get(x, None) is None:
            b[x] = c
            c += 1
    return b

def main(argv=sys.argv):
    """Sort a list of footnotes.
    """

    # Handle cmdline parameters...
    if len(argv) < 2 or ('-a' in argv and (len(argv) < 3)):
        print "Usage: %s [-a] <filepath>" % (argv[0])
        return

    # Read the source into memory...
    text, footnotes = open(argv[-1], 'r').read().split('@footnotes:')

    # Create a mapping of references to their order number in text...
    text_ref_map = '-a' in argv and get_ref_map(re.findall('\[\d+\]', text))

    if not '-a' in argv:
        # Just order the footnotes...
        sorter = lambda x: ']' in x and int(x[1:x.index(']')]) or 0
        footnotes = sorted(footnotes.splitlines(), key=sorter)
    else:
        # Replace reference numbers to an ordered ascending list...
        text = re.sub('\[\d+\]',
                      lambda x: "[%s]" % text_ref_map.get(x.group(0),'???'),
                      text)
        # Order footnotes by their new number...
        sorter = lambda x: ']' in x and text_ref_map.get(x[:x.index(']')+1], 0)
        
        # Replace footnote numbers...
        footnotes = map(lambda x: "]" in x and "[%s]%s" % (
            text_ref_map.get(x[:x.index(']')+1], 0), x[x.index(']')+1:]) or x,
                        sorted(footnotes.splitlines(), key=sorter))

    footnotes = '\n'.join(footnotes)

    print "%s\n@footnotes:\n%s" % (text, footnotes)
    

if __name__ == '__main__':
    if '--test' in sys.argv:
        import doctest
        doctest.testmod()
    else:
        main()
