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)
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)
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)