################################################################################
# #
# This file is part of the Bibolamazi Project. #
# Copyright (C) 2014 by Philippe Faist #
# philippe.faist@bluewin.ch #
# #
# Bibolamazi is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# Bibolamazi is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with Bibolamazi. If not, see <http://www.gnu.org/licenses/>. #
# #
################################################################################
"""
This module contains the code that implements Bibolamazi's main functionality. It also
provides the basic tools for the command-line interface.
"""
import os
import os.path
import re
import sys
import argparse
import textwrap
import types
from collections import namedtuple
import logging
# import all the parts we need from our own application.
# ------------------------------------------------------
import bibolamazi.init
# rest of the modules
from . import blogger
from . import version
from .bibolamazifile import BibolamaziFile
from .bibfilter import BibFilter
from . import argparseactions
from . import butils
from .butils import BibolamaziError
# for list of filters
from .bibfilter import factory as filterfactory
# our logger for the main module
logger = logging.getLogger(__name__)
# ------------------------------------------------------------------------------
# code to set up logging mechanism, if run by command-line
[docs]def verbosity_logger_level(verbosity):
"""
Simple mapping of 'verbosity level' (used, for example for command line
options) to correspondig logging level (:py:const:`logging.DEBUG`,
:py:const:`logging.ERROR`, etc.).
"""
if verbosity == 0:
return logging.ERROR
elif verbosity == 1:
return logging.INFO
elif verbosity == 2:
return logging.DEBUG
elif verbosity >= 3:
return blogger.LONGDEBUG
raise ValueError("Bad verbosity level: %r" %(verbosity))
# ------------------------------------------------------------------------------
[docs]class BibolamaziNoSourceEntriesError(BibolamaziError):
def __init__(self):
msg = "Error: No source entries found. Stopping before we overwrite the bibolamazi file.";
BibolamaziError.__init__(self, msg);
[docs]def setup_filterpackage_from_argstr(argstr):
"""
Add a filter package definition and path to filterfactory.filterpath from a string
that is a e.g. a command-line argument to --filterpath or a part of the environment
variable BIBOLAMAZI_FILTER_PATH.
"""
if not argstr:
return
fpparts = argstr.split('=',1)
fpname = fpparts[0]
fpdir = fpparts[1] if len(fpparts) >= 2 and fpparts[1] else None
if re.search(r'[^a-zA-Z0-9_\.]', fpname):
raise BibolamaziError("Invalid filter package: `%s': not a valid python identifier. "
"Did you get the filterpackage syntax wrong? "
"Syntax: '<packagename>[=<path>]'." %(fpname))
try:
ok = filterfactory.validate_filter_package(fpname, fpdir, raise_exception=True)
except filterfactory.NoSuchFilterPackage as e:
raise BibolamaziError(unicode(e))
filterfactory.filterpath[fpname] = fpdir
[docs]def setup_filterpackages_from_env():
if 'BIBOLAMAZI_FILTER_PATH' in os.environ:
logger.debug("Detected BIBOLAMAZI_FILTER_PATH=%s, using it" %(os.environ['BIBOLAMAZI_FILTER_PATH']))
for fp in reversed(os.environ['BIBOLAMAZI_FILTER_PATH'].split(os.pathsep)):
setup_filterpackage_from_argstr(fp)
[docs]class AddFilterPackageAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
setup_filterpackage_from_argstr(values)
[docs]def get_args_parser():
parser = argparse.ArgumentParser(
description='Prepare consistent BibTeX files for your LaTeX documents',
prog='bibolamazi',
epilog="Log messages will be produced in color by default if outputting to a TTY. To override "
"the use of TTY colors, set environment variable BIBOLAMAZI_TTY_COLORS to 'yes', 'no' or 'auto'.",
add_help=False);
parser.add_argument('-N', '--new', action=argparseactions.opt_init_empty_template, nargs=1,
metavar="[new_filename.bib]",
help="Create a new bibolamazi file with a template configuration.");
parser.add_argument('-F', '--list-filters', action=argparseactions.opt_list_filters, dest='list_filters',
help="Show a list of available filters along with their description, and exit.");
parser.add_argument('-C', '--no-cache', action='store_false', dest='use_cache', default=True,
help="Bypass and ignore any existing cache file, and regenerate the cache. If "
"the cache file exists, it will be overwritten.");
parser.add_argument('-z', '--cache-timeout', dest='cache_timeout', type=butils.parse_timedelta,
default=None,
help="The default timeout after which to consider items in cache to be invalid. "
"Not all cache items honor this. Format: '<N><unit>' with unit=w/d/m/s");
parser.add_argument('--help', '-h', action=argparseactions.opt_action_help, nargs='?',
metavar='filter',
help='Show this help message and exit. If filter is given, show information and '
'help text for that filter. See --list-filters for a list of available filters.')
parser.add_argument('--version', action=argparseactions.opt_action_version, nargs=0,
help='Show bibolamazi version number and exit.')
parser.add_argument('--filterpackage', action=AddFilterPackageAction,
help="Add a package name in which to search for filters. You may specify this "
"option multiple times; last specified filter packages are searched first. Valid "
"values for this option are a simple python package name (if it is in the "
"PYTHONPATH), or a pair 'package=/some/location' where package is the python "
"package name, which will be loaded with the given path prepended to sys.path.")
parser.add_argument('--verbosity', action=argparseactions.opt_set_verbosity, nargs=1,
help="Set verbosity level (0=quiet, 1=info (default), 2=verbose, 3=long debug).")
parser.add_argument('-q', '-v0', '--quiet', action=argparseactions.opt_set_verbosity, nargs=0, const=0,
help="Don't display any messages (same as --verbosity=0)");
parser.add_argument('-v1', action=argparseactions.opt_set_verbosity, nargs=0, const=1,
help='Set normal verbosity mode (same as --verbosity=1)')
parser.add_argument('-v', '-v2', '--verbose', action=argparseactions.opt_set_verbosity, nargs=0, const=2,
help='Set verbose mode (same as --verbosity=2)')
parser.add_argument('-vv', '-v3', '--long-verbose', action=argparseactions.opt_set_verbosity, nargs=0, const=3,
help='Set very verbose mode, with long debug messages (same as --verbosity=3)')
parser.add_argument('--fine-log-levels', action=argparseactions.opt_set_fine_log_levels,
help=textwrap.dedent('''\
Fine-grained logger control: useful for debugging filters or
bibolamazi itself. This is a comma-separated list of modules and
corresponding log levels to set, e.g.
"core=INFO,filters=DEBUG,filters.arxiv=LONGDEBUG", where if in an
item no module is given (but just a level or number), then the
root logger is addressed. Possible levels are (%s)
''')%(
", ".join( (x[0] for x in blogger.LogLevel.levelnos) )
))
parser.add_argument('bibolamazifile',
help='The .bibolamazi.bib file to update, i.e. that contains the %%%%%%-BIB-OLA-MAZI '
'configuration tags.');
return parser
ArgsStruct = namedtuple('ArgsStruct', ('bibolamazifile', 'use_cache', 'cache_timeout'));
[docs]def main(argv=sys.argv[1:]):
try:
# run main program
_main_helper(argv)
except SystemExit:
raise
except KeyboardInterrupt:
raise
except BibolamaziError as e:
logger.error("[BIBOLAMAZI ERROR]\n" + unicode(e))
except:
print
print " -- EXCEPTION --"
print
# debugging post-mortem
import traceback; traceback.print_exc()
import pdb; pdb.post_mortem();
def _main_helper(argv):
# get some basic logging mechanism running
blogger.setup_simple_console_logging()
# start with level INFO
logging.getLogger().setLevel(logging.INFO)
# load precompiled filters, if we've got any
# ------------------------------------------
#try:
# import bibolamazi.bibolamazi_compiled_filter_list as pc
# filters_factory.load_precompiled_filters('bibolamazi.filters', dict([
# (fname, pc.__dict__[fname]) for fname in pc.filter_list
# ]))
#except ImportError:
# pass
# set up extra filter packages from environment variables
# -------------------------------------------------------
setup_filterpackages_from_env()
# parse the command line arguments
# --------------------------------
parser = get_args_parser()
args = parser.parse_args(args=argv);
return run_bibolamazi_args(args)
[docs]def run_bibolamazi(bibolamazifile, **kwargs):
# defaults
kwargs2 = {
'use_cache': True,
'cache_timeout': None,
}
kwargs2.update(kwargs);
args = ArgsStruct(bibolamazifile, **kwargs2)
return run_bibolamazi_args(args)
[docs]def run_bibolamazi_args(args):
#
# args is supposed to be the parsed arguments from main()
#
logger.debug(textwrap.dedent("""
Bibolamazi Version %(ver)s by Philippe Faist (C) 2014
Use option --help for help information.
""" % {
'ver': version.version_str
}));
# open the bibolamazifile, which is the main bibtex file
# ------------------------------------------------------
kwargs = {
'use_cache': args.use_cache
}
#
# If given a cache_timeout, give it as parameter
#
if args.cache_timeout is not None:
logger.debug("default cache timeout: %r", args.cache_timeout)
kwargs['default_cache_invalidation_time'] = args.cache_timeout
# open the bibolamazi file and create the BibolamaziFile object. This will parse the rules
# and the entries, as well as keep some information on how to re-write to the file.
bfile = BibolamaziFile(args.bibolamazifile, **kwargs)
bibdata = bfile.bibliographyData();
if (bibdata is None or not len(bibdata.entries)):
logger.critical("No source entries found. Stopping before we overwrite the bibolamazi file.");
raise BibolamaziNoSourceEntriesError()
# now, run the selected filters in the corresponding order.
# ---------------------------------------------------------
for filtr in bfile.filters():
#
# For debugging: dump the library at each filter step on level longdebug()
#
if logger.isEnabledFor(blogger.LONGDEBUG):
s = "========== Dumping Bibliography Database ==========\n"
for key, entry in bibdata.entries.iteritems():
s += " %10s: %r\n\n"%(key, entry)
s += "===================================================\n"
logger.longdebug(s)
#
# See how the filter acts. It can act on the full bibolamazifile object, it can act on the
# full list of entries (possibly adding/deleting entries etc.), or it can act on a single
# entry.
#
action = filtr.action();
logger.info("Filter: %s" %(filtr.getRunningMessage()));
filtr.prerun(bfile)
#
# pass the whole bibolamazifile to the filter. the filter can actually do
# whatever it wants with it (!!)
#
if (action == BibFilter.BIB_FILTER_BIBOLAMAZIFILE):
filtr.filter_bibolamazifile(bfile);
logger.debug('filter '+filtr.name()+' filtered the full bibolamazifile.');
continue
#
# filter all the bibentries one by one throught the filter. The filter can only
# process a single bibentry at a time.
#
if (action == BibFilter.BIB_FILTER_SINGLE_ENTRY):
bibdata = bfile.bibliographyData();
for (k, entry) in bibdata.entries.iteritems():
filtr.filter_bibentry(entry);
bfile.setBibliographyData(bibdata);
logger.debug('filter '+filtr.name()+' filtered each of the the bibentries one by one.');
continue
raise ValueError("Bad value for BibFilter.action(): "+repr(action));
# and output everything back to the original file.
bfile.saveToFile();
logger.debug('Done.');
return None
if __name__ == "__main__":
main()