Source code for schrodinger.application.jaguar.user_config
"""
User profiles and configuration support for Jaguar input files
"""
# Contributors: Mark A. Watson
import contextlib
import io
import os
from distutils import file_util
from schrodinger.utils import fileutils
CONFIG_FILE = 'jaguar.config'
_macros = {} # lazy instantiated macros dictionary
[docs]class JaguarConfigError(Exception):
pass
def _load_user_macros(fh):
"""
Import the Jaguar configuration file. The format is:
<name1>
key1=value1
key2=value2
...
<name2>
keyA=valueA
keyB=valueB
where <name> is differentiated as any line NOT containing a "=" character.
:type fh: open file handle
:param fh: file handle to an open, readable Jaguar configuration file
"""
key = None
macros = {}
for line in fh:
if line.strip():
# Ignore blank lines
if key and '=' in line:
# Save macro body
macros[key].append(line.rstrip())
elif '=' not in line:
# Initialize new macro
key = line.strip()
if key in macros:
msg = "Error: macro name %s is defined multiple times in " % key
msg += "configuration file: %s\n" % fh.name
msg += "Please delete all but one definition.\n"
raise JaguarConfigError(msg)
macros[key] = []
return macros
def _get_macro_dict():
"""
Return the dictionary of macros.
"""
config_file = get_config_filename()
if os.path.isfile(config_file):
with open(config_file, 'r') as fh:
macros = _load_user_macros(fh)
else:
macros = {}
return macros
def _write_preprocessed_infile(macros, infile, outfile):
"""
Write expanded file <outfile> based on file <infile> and macro definitions.
:type infile: str
:param infile: name of file to be preprocessed
:type outfile: str
:param outfile: name of postprocessed file
"""
# Write to memory buffer first in case infile == outfile
with contextlib.closing(io.StringIO()) as buff:
with open(infile, 'r') as ifh:
for line in ifh:
if line.strip() in macros:
for item in macros[line.strip()]:
buff.write(item + '\n')
else:
buff.write(line)
# Write memory buffer to outfile
try:
with open(outfile, 'w') as ofh:
ofh.write(buff.getvalue())
except IOError as e:
msg = str(e)
msg += '\n\nFailed to write to file %s\n' % outfile
msg += 'Try adding write permissions to your input files.'
raise JaguarConfigError(msg)
[docs]def get_config_filename():
"""
Return path to Jaguar config file
"""
return os.path.join(fileutils.get_directory_path(fileutils.APPDATA),
CONFIG_FILE)
[docs]def preprocess_infile(infile, outfile):
"""
Preprocess macros in file <infile> and write postprocessed file <outfile>.
If files names are the same, <infile> is overwritten.
:type infile: str
:param infile: name of file to be preprocessed
:type outfile: str
:param outfile: name of postprocessed file
"""
macros = _get_macro_dict()
if infile != get_config_filename():
if macros:
# Create preprocessed input file
_write_preprocessed_infile(macros, infile, outfile)
elif infile != outfile:
# Simply copy infile to outfile
file_util.copy_file(infile, outfile, update=True)
[docs]def get_macro(name, reread=False):
"""
Return the dictionary of keywords set by the jaguar macro called name.
If the macro does not exist, a ValueError is thrown.
The function will lazily initialize the global _macros variable.
:param name: name of macro to convert into a dictionary
:type name: str
:param reread: whether the configuration file should be re-read
:type reread: bool
:return: dictionary of jaguar keywords
:rtype: dict
:raise ValueError: if name is not in the dictionary of known macros
:raise JaguarConfigError: if there are problems with the config file
"""
global _macros
if not _macros or reread:
_macros = _get_macro_dict()
try:
macro = _macros[name]
except:
raise ValueError("Macro {} unknown.".format(name))
# note a macro is only allowed to have key=value pairs in it (no macros)
macro_dict = dict(
(k.strip(), v.strip()) for k, v in (kv.split('=', 1) for kv in macro))
return macro_dict