예제 #1
0
    def reload(self):
        """(Re-)loads the database from filesystem."""
        parser = configparser.ConfigParser()
        try:
            with io.open(self.configPath, encoding="UTF-8") as f:
                confFile = f.read()
        except IOError:
            raise DatabaseFormatError(
                'Invalid BibTeX VCS directory "{}": '
                "Configuration file bibtexvcs.conf not found.".format(self.directory)
            )
        # workaround because ConfigParser does not support sectionless entries
        try:
            parser.read_string("[root]\n" + confFile)
        except configparser.Error as e:
            raise DatabaseFormatError("Could not parse configuration file '{}':\n\n{}".format(BTVCSCONF, e))
        config = parser["root"]

        self.bibfileName = config.get("bibfile", "references.bib")
        self.journalsName = config.get("journals", "journals.txt")

        for path in self.bibfilePath, self.journalsPath:
            if not exists(path):
                open(path, "a").close()
                self.vcs.add(relpath(path, self.directory))

        self.bibfile = BibFile(join(self.directory, self.bibfileName))
        self.journals = JournalsFile(join(self.directory, self.journalsName))

        self.name = config.get("name", "Untitled Bibtex Database")
        self.documents = config.get("documents", "Documents")

        self.makeJournalBibfiles()  # ensure these are up-to-date

        self.publicLink = config.get("publicLink", None)
        if not exists(self.documentsPath):
            os.mkdir(self.documentsPath)
예제 #2
0
class Database:
    """Represents a complete literature database.

    :param directory: Base path of the database.
    :type directory: str

    The database configuration is read from the file `bbibtexvcs.conf` and from the layout of the
    directory that file resides in.

    .. attribute:: directory

        Absolute path of the root directory of the literature database.

    .. attribute:: bibfileName

        Name of the main bibtex database file.

    .. attribute:: bibfile

        :class:`BibFile` object parsed from the bibtex file.

    .. attribute:: journalsName

        Name of the journals file.

    .. attribute:: journals

        :class:`JournalsFile` object read from the journals file.

    .. attribute:: name

        Name of the database.

    .. attribute:: documents

        Name of the documents directory (relative to :attr:`directory`).

    .. attribute:: vcsType

        Type of the used VCS system. One of: ``"git"``, ``"mercurial"``, or ``None``
    """

    def __init__(self, directory):
        self.directory = directory
        if exists(join(self.directory, '.git')):
            self.vcsType = 'git'
        elif exists(join(self.directory, '.hg')):
            self.vcsType = 'mercurial'
        else:
            self.vcsType = None
        self._vcs = None
        self.reload()

    def reload(self):
        """(Re-)loads the database from filesystem."""
        parser = configparser.ConfigParser()
        try:
            with io.open(self.configPath, encoding='UTF-8') as f:
                confFile = f.read()
        except IOError:
            raise DatabaseFormatError('Not a valid BibTeX VCS directory: '
                                      'Configuration file bibtexvcs.conf not found.')
        # workaround because ConfigParser does not support sectionless entries
        try:
            parser.read_string("[root]\n" + confFile)
        except configparser.Error as e:
            raise DatabaseFormatError("Could not parse configuration file '{}':\n\n{}"
                                      .format(BTVCSCONF, e))
        config = parser['root']

        self.bibfileName = config.get('bibfile', 'references.bib')
        self.journalsName = config.get('journals', 'journals.txt')

        for path in self.bibfilePath, self.journalsPath:
            if not exists(path):
                open(path, 'a').close()
                self.vcs.add(relpath(path, self.directory))

        self.bibfile = BibFile(join(self.directory, self.bibfileName))
        self.journals = JournalsFile(join(self.directory, self.journalsName))

        self.name = config.get('name', "Untitled Bibtex Database")
        self.documents = config.get('documents', 'Documents')

        self.makeJournalBibfiles()  # ensure these are up-to-date

        self.publicLink = config.get('publicLink', None)
        if not exists(self.documentsPath):
            os.mkdir(self.documentsPath)

    @property
    def journalsPath(self):
        """The absolute path of the `journals` file."""
        return join(self.directory, self.journalsName)

    @property
    def documentsPath(self):
        """Absolute path of the `documents` directory."""
        return join(self.directory, self.documents)

    @property
    def bibfilePath(self):
        """Absolute path of the bib file."""
        return join(self.directory, self.bibfileName)

    @property
    def configPath(self):
        """Absolute path of the conf file."""
        return join(self.directory, BTVCSCONF)

    @property
    def checksPath(self):
        """Absolute path of the (optional) checks file."""
        return join(self.directory, 'checks.py')

    def referencedDocuments(self):
        """Returns a list of all filenames of documents referenced to in the bib file."""
        docs = []
        for entry in self.bibfile.values():
            fname = entry.filename()
            if fname:
                docs.append(fname)
        return docs

    def existingDocuments(self):
        """Walks recursively through the :attr:`documents` directory and return the paths of all
        files contained in there, relative to :attr:`documentsPath`.
        """
        for dirpath, _, filenames in os.walk(self.documentsPath):
            for file in filenames:
                if file != '.DS_Store':
                    yield relpath(join(dirpath, file), self.documentsPath)

    def strval(self, value):
        """Returns a string value for *value*. If *value* is a :class:`MacroReference`, substitutes
        the value (if known).
        """
        if isinstance(value, MacroReference):
            if value.name in self.bibfile.macroDefinitions:
                return self.bibfile.macroDefinitions[value.name]
            if value.name in self.journals:
                return self.journals[value.name].full
        return str(value)

    def makeJournalBibfiles(self):
        """Creates or updates the files containing journal macro definitions in full and
        abbreviated form, respectively.
        """
        base = self.bibfilePath[:-4]
        if not exists(base + '_abbr.bib') or not exists(base + '_full.bib') or \
                os.path.getmtime(self.journalsPath) > os.path.getmtime(base + '_abbr.bib'):
            self.journals.writeBibfiles(base)

    def runJabref(self):
        """Tries to open this database's ``.bib`` file with `JabRef`_. Will do the following:

        - If there is a file named ``jabref.jar`` in :attr:`directory`, it is run with the
          java interpreter through ``java -jar jabref.jar``. If additionally there is a file
          ``jabref.prefs``, JabRef`s options are imported from that file.
        - Otherwise, the command ``jabref`` is executed. To that end, the ``jabref`` binary must
          be in the system's ``PATH``.

        :returns: The :class:`subprocess.Popen` object corresponding to JabRef process.
        """
        shell = False
        if exists(join(self.directory, 'jabref.jar')):
            if os.name == 'nt':
                cmdline = ['start', 'jabref.jar']
                shell = True
            else:
                cmdline = ['java', '-jar', 'jabref.jar']
        else:
            cmdline = ['jabref']
        if exists(join(self.directory, 'jabref.prefs')):
            cmdline += ['--primp', join('jabref.prefs')]
        else:
            cmdline += ['--primp', resource_filename(__name__, 'defaultJabref.prefs')]
        cmdline.append(os.curdir + os.sep + self.bibfileName)
        try:
            return subprocess.Popen(cmdline, shell=shell, cwd=self.directory)
        except FileNotFoundError as fnf:
            if cmdline[0] in ('java', 'start'):
                fnf.strerror = 'Please install Java from http://java.com.'
            elif cmdline[0] == 'jabref':
                fnf.strerror = 'Please install JabRef from http://jabref.sf.net.'
            raise fnf

    @property
    def vcs(self):
        """The :class:`VCSInterface` object associated to this db.

        Will be created on first access.
        """
        if self._vcs is None:
            self._vcs = VCSInterface.get(self)
        return self._vcs

    def export(self, templateString=None, docDir=None):
        """Exports the BibTeX database to a string by using the jinja template engine."""
        import datetime, hashlib
        try:
            import jinja2
        except ImportError:
            raise ImportError('You need to install the jinja2 package in order to export.')
        if docDir is None:
            docDir = self.documentsPath
        def md5filter(value):
            return hashlib.md5(value.encode()).hexdigest()
        env = jinja2.Environment(autoescape=False)
        env.filters['md5'] = md5filter
        if templateString is None:
            templateString = resource_string(__name__, 'defaultTemplate.html').decode('UTF-8')
        template = env.from_string(templateString)
        revision = self.vcs.revision()
        import locale
        locale.setlocale(locale.LC_ALL, '')
        now = datetime.datetime.now().strftime('%c')
        version = get_distribution('bibtexvcs').version
        return template.render(database=self, docDir=docDir, version=version, revision=revision, now=now)
예제 #3
0
class Database:
    """Represents a complete literature database.

    The database configuration is read from the file `bbibtexvcs.conf` and from the layout of the
    directory that file resides in.

    Parameters
    ----------
    directory : str
        Base path of the database.

    Attributes
    ----------
    directory : str
        Absolute path of the root directory of the literature database.
    bibfileName : str
        Name of the main bibtex database file.
    bibfile : :class:`BibFile`
        :class:`BibFile` object parsed from the bibtex file.
    journalsName : str
        Name of the journals file.
    journals : :class:`JournalsFile`
        :class:`JournalsFile` object read from the journals file.
    name : str
        Name of the database.
    documents : str
        Name of the documents directory (relative to :attr:`directory`).
    vcsType : ("git", "mercurial", "local")
        Type of the used VCS system.
    """

    def __init__(self, directory, vcs=None):
        self.directory = directory
        if exists(join(self.directory, ".git")):
            self.vcsType = "git"
        elif exists(join(self.directory, ".hg")):
            self.vcsType = "mercurial"
        else:
            self.vcsType = "local"
        self._vcs = vcs
        self.reload()

    def reload(self):
        """(Re-)loads the database from filesystem."""
        parser = configparser.ConfigParser()
        try:
            with io.open(self.configPath, encoding="UTF-8") as f:
                confFile = f.read()
        except IOError:
            raise DatabaseFormatError(
                'Invalid BibTeX VCS directory "{}": '
                "Configuration file bibtexvcs.conf not found.".format(self.directory)
            )
        # workaround because ConfigParser does not support sectionless entries
        try:
            parser.read_string("[root]\n" + confFile)
        except configparser.Error as e:
            raise DatabaseFormatError("Could not parse configuration file '{}':\n\n{}".format(BTVCSCONF, e))
        config = parser["root"]

        self.bibfileName = config.get("bibfile", "references.bib")
        self.journalsName = config.get("journals", "journals.txt")

        for path in self.bibfilePath, self.journalsPath:
            if not exists(path):
                open(path, "a").close()
                self.vcs.add(relpath(path, self.directory))

        self.bibfile = BibFile(join(self.directory, self.bibfileName))
        self.journals = JournalsFile(join(self.directory, self.journalsName))

        self.name = config.get("name", "Untitled Bibtex Database")
        self.documents = config.get("documents", "Documents")

        self.makeJournalBibfiles()  # ensure these are up-to-date

        self.publicLink = config.get("publicLink", None)
        if not exists(self.documentsPath):
            os.mkdir(self.documentsPath)

    def setDefault(self):
        """Set this database as default in config."""
        from bibtexvcs import config

        config.setDefaultDatabase(self)

    @classmethod
    def getDefault(cls):
        from bibtexvcs import config

        directory = config.getDefaultDirectory()
        if directory is None:
            raise NoDefaultDatabaseError("No default database configured")
        return cls(directory)

    @property
    def journalsPath(self):
        """The absolute path of the `journals` file."""
        return join(self.directory, self.journalsName)

    @property
    def documentsPath(self):
        """Absolute path of the `documents` directory."""
        return join(self.directory, self.documents)

    @property
    def bibfilePath(self):
        """Absolute path of the bib file."""
        return join(self.directory, self.bibfileName)

    @property
    def configPath(self):
        """Absolute path of the conf file."""
        return join(self.directory, BTVCSCONF)

    @property
    def checksPath(self):
        """Absolute path of the (optional) checks file."""
        return join(self.directory, "checks.py")

    def referencedDocuments(self):
        """Returns a list of all filenames of documents referenced to in the bib file."""
        docs = []
        for entry in self.bibfile.values():
            fname = entry.filename()
            if fname:
                docs.append(fname)
        return docs

    def existingDocuments(self):
        """Walks recursively through the :attr:`documents` directory and return the paths of all
        files contained in there, relative to :attr:`documentsPath`.
        """
        for dirpath, _, filenames in os.walk(self.documentsPath):
            for file in filenames:
                if file != ".DS_Store":
                    yield relpath(join(dirpath, file), self.documentsPath)

    def strval(self, value):
        """Returns a string value for *value*. If *value* is a :class:`MacroReference`, substitutes
        the value (if known).
        """
        if isinstance(value, MacroReference):
            if value.name in self.bibfile.macroDefinitions:
                return self.bibfile.macroDefinitions[value.name]
            if value.name in self.journals:
                return self.journals[value.name].full
        return str(value)

    @property
    def abbrJournalsName(self):
        """Name of the abbreviated journals string file."""
        return self.bibfileName[:-4] + "_abbr.bib"

    @property
    def fullJournalsName(self):
        """Name of the full journals string file."""
        return self.bibfileName[:-4] + "_full.bib"

    def makeJournalBibfiles(self):
        """Creates or updates the files containing journal macro definitions in full and
        abbreviated form, respectively.
        """
        abbr, full = [join(self.directory, name) for name in (self.abbrJournalsName, self.fullJournalsName)]
        if not exists(full) or not exists(abbr) or os.path.getmtime(self.journalsPath) > os.path.getmtime(abbr):
            self.journals.writeBibfiles(self.bibfilePath[:-4])

    def runJabref(self):
        """Tries to open this database's ``.bib`` file with `JabRef`_. Will do the following:

        - If there is a file named ``jabref.jar`` in :attr:`directory`, it is run with the
          java interpreter through ``java -jar jabref.jar``. If additionally there is a file
          ``jabref.prefs``, JabRef`s options are imported from that file.
        - Otherwise, the command ``jabref`` is executed. To that end, the ``jabref`` binary must
          be in the system's ``PATH``.

        :returns: The :class:`subprocess.Popen` object corresponding to JabRef process.
        """
        shell = False
        if exists(join(self.directory, "jabref.jar")):
            if os.name == "nt":
                cmdline = ["start", "jabref.jar"]
                shell = True
            else:
                cmdline = ["java", "-jar", "jabref.jar"]
        else:
            cmdline = ["jabref"]
        if exists(join(self.directory, "jabref.prefs")):
            cmdline += ["--primp", join("jabref.prefs")]
        else:
            cmdline += ["--primp", resource_filename(__name__, "defaultJabref.prefs")]
        cmdline.append(os.curdir + os.sep + self.bibfileName)
        try:
            return subprocess.Popen(cmdline, shell=shell, cwd=self.directory)
        except FileNotFoundError as fnf:
            if cmdline[0] in ("java", "start"):
                fnf.strerror = "Please install Java from http://java.com."
            elif cmdline[0] == "jabref":
                fnf.strerror = "Please install JabRef from http://jabref.sf.net."
            raise fnf

    @property
    def vcs(self):
        """The :class:`VCSInterface` object associated to this db.

        Will be created on first access.
        """
        if self._vcs is None:
            self._vcs = VCSInterface.get(self)
        return self._vcs

    def export(self, templateString=None, docDir=None):
        """Exports the BibTeX database to a string by using the jinja template engine."""
        import datetime, hashlib, bibtexvcs

        try:
            import jinja2
        except ImportError:
            raise ImportError("You need to install the jinja2 package in order to export.")
        if docDir is None:
            docDir = self.documentsPath

        def md5filter(value):
            return hashlib.md5(value.encode()).hexdigest()

        env = jinja2.Environment(autoescape=False)
        env.filters["md5"] = md5filter
        if templateString is None:
            templateString = resource_string(__name__, "defaultTemplate.html").decode("UTF-8")
        template = env.from_string(templateString)
        revision = self.vcs.revision()
        import locale

        locale.setlocale(locale.LC_ALL, "")
        now = datetime.datetime.now().strftime("%c")
        version = bibtexvcs.__version__
        return template.render(database=self, docDir=docDir, version=version, revision=revision, now=now)