#!/usr/bin/env python
'''
Script to read an *.aux file generated by latex and extract all the references to a
new bibtex file

'''
import sys
import re
import os

################################################################################
# CUSTOMIZE THESE VARIABLES if needed
dumpfile= os.getenv('BIBDB',os.path.join(os.environ['HOME'],'texmf/bibtex/bib/bib.dmp'))
#*******************************************************************************
encoding='utf8'
import yapbib.biblist as biblist

rex_bibfile= re.compile(r'\\bibliography\{([^}]*)\}')
rex_bibdata= re.compile(r'\\bibdata\{([^}]*)\}')
rex_texcite= re.compile(r'\\(?:no)?cite(?:alt|p|t|author|year)?[\*]?(?:\[[^]]*\]){0,2}\{([^}]*)\}',re.M)
rex_auxcite= re.compile(r'\\citation{([^}]*)}')
rex_auxinput= re.compile(r'\\@input{([^}]+)}')

def get_strng_field(k):
  """Parse arguments of the form string:field"""
  l= unicode(k,encoding=encoding).split(':')
  if len(l) == 1:
    ff= []
    ss= l[0]
  elif len(l) == 2:
    if l[0]=='': ss= '*' # Search all strings
    else:        ss= l[0]
    if l[1]=='':   ff= [] # Search in all fields
    else:          ff= l[1].split(':')
  return ss,ff

def parse_texfile(texfile):
  reflist=[]
  fi= open(texfile);  s= fi.readlines(); fi.close()
  for line in s:
    line= line[:line.find('%')]
    for m in rex_texcite.finditer(line):
      if m!= None:  reflist.extend(m.group(1).split(','))
  return make_unique(reflist)

def parse_auxfile(auxfile): 
  """Parse an auxfile (recursing if necessary) and return citation list"""
  reflist=[]
  fi= open(auxfile);  s= fi.readlines(); fi.close()
  for line in s:
    m1= rex_auxinput.search(line) # Search input of aux file
    if m1: reflist.extend(parse_auxfile(m1.group(1)))
    m= rex_auxcite.search(line)   # Seach for citations
    if m != None:  reflist.append(m.group(1))
  return make_unique(reflist)

def make_unique(lista):
  """Simpler (may fail if lista is not a list) unique function"""
  seen = []
  return list(c for c in lista if not (c in seen or seen.append(c)))

def remove_fields(item, cond):
  ff,ty= get_strng_field(cond)
  if item.get_field('_type') in [t.lower() for t in ty] or ty == []:
    if item.get_field(ff) != None:
      del(item[ff])            

def main():
  import optparse
  usage = """usage: %prog [options] datafile1 [datafile2 ...]
  Extracts a BibTeX database according to an aux or tex file. 
  Keeps only those items that are cited

  DESCRIPTION It reads an *.aux file as produced by LaTeX or a *.tex file directly
       and writes to standard output a bibtex file containing exactly the
       bibtex entries refereed in the aux file.

  NOTE:  If the environment variable BIBDB is set, this is used as bibliography database

  ************************************************************
  USE %prog --help for details
  ************************************************************  
  """
  
  parser = optparse.OptionParser(usage)

  parser.add_option("-d", "--database", action='append', type='string'
                    , help="Database to use, default: %s. May be used more than once" %(dumpfile))

  parser.add_option("-l", "--list", action="store_true", dest="list", default=False
                    , help="List cited keys to stdout (screen)")

  parser.add_option("-o", "--output", default=None
                    , help="Output file. Use '-' for stdout (screen)")

  parser.add_option("", "--remove-common", action="store_true", default=False
                    , help="Remove \"url from articles\", \"doi, issn, month and abstracts from everything\"")

  parser.add_option("", "--remove-fields", action='append', type='string'
                    , help="Remove fields from types. Notations is \"field:type1,type2,..,typen\" to remove field from these types (for instance ARTICLES and BOOKS but not for INPROCEEDINGS), Use \"field\" (with no \":\") for removing the field for all types. It can be used more than once for removing several fields")

  parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False
                    , help="Give some informational messages.")

  (op, args) = parser.parse_args()

  if len(args) < 1:
    parser.error("Incorrect number of arguments")

  if op.database== None:
    dbfiles=[dumpfile]
  else:
    dbfiles=op.database

  # Read the database(s)
  b= biblist.BibList()
  for fname in dbfiles:
    if op.verbose:  print '# Loading database %s ...' %(fname)
    failed=False
    if '.dmp' in fname:
      try:    b.load(fname)
      except: failed=True
    elif '.bib' in fname:
      try: b.import_bibtex(fname, normalize=modify_keys)
      except: failed=True
    else: failed= True

    if failed: 
      mensaje= 'Database file %s not found or failed to load. Set the name as an option or set the environment variable BIBDB'%(fname)
      parser.error(mensaje)

  if op.output == None:  output=os.path.splitext(args[0])[0]+'.bib'
  else:    output= op.output

  # Set fields to remove
  rem=[]
  if op.remove_common:
    rem= rem + ['url:article', 'issn', 'doi', 'month', 'abstract']
  if op.remove_fields != None:
    rem= rem + op.remove_fields

########################################################################
  for fname in args:
    if fname.endswith('tex'):
      cit= parse_texfile(fname)
    elif fname.endswith('aux'):
      cit= parse_auxfile(fname)
    else:
      parser.error('Incorrect argument "%s"'%(fname))

  if op.list:
    print '\n'.join(sorted(cit))

  bout=biblist.BibList()

  for k in cit:
    if k in b.ListItems:
      item= b.get_item(k)
      if rem != []:
        for cond in rem:  remove_fields(item, cond)
      bout.add_item(item, k)
    else:
      print '# Warning: %s not found in database' %(k)
  mensaje='# created with:  %s\n'%(' '.join(sys.argv))
  fi= open(output,'w');  fi.write(mensaje+bout.to_bibtex());  fi.close()
  # bout.output(output,formato='bibtex')
  if op.verbose: print 'Items saved to %s' %(output)


if __name__ == "__main__": main()



### Local Variables: 
### tab-width: 2
### END:
