"""
Functions used for updating job names. `update_jobname` is intended for use
outside of AF2.
"""
import enum
import os
import re
import schrodinger
maestro = schrodinger.get_maestro()
# A regex used to classify job names. This regex will capture four groups:
# 1. The modified job name prefix
# 2. The standard job name
# 3. The modified job name suffix
# 4. The entire custom job name
MOD_JOBNAME_RE = r"""
(?:(?:(.*) # An optional prefix
(%s) # The standard job name (to be interpolated later)
(.*?)) # An optional suffix. Note that this is a non-greedy match
# so that it doesn't consume the uniquifying digits.
|(.*?)) # A custom job name if we can't match the standard job name
"""
# An optional trailing underscore and uniquifying digits
MOD_JOBNAME_RE_UNIQUIFY = r"""(?:_[\d]+)?"""
[docs]class JobnameType(enum.Enum):
"""
Constants describing the three categories of job names
"""
Standard = 1
"""The standard job name generated by the panel"""
Modified = 2
"""The standard job name plus a user-added prefix or suffix (or both)"""
Custom = 3
"""A user-generated job name that doesn't contain the standard job name"""
def _is_increment_job_name_set():
"""
Default behavior is to increment the job name. Don't increment if running
from maestro and its increment preference is off.
"""
return not (maestro and maestro.get_command_option(
"prefer", "incrementjobname") == "False")
[docs]def uniquify(jobname,
jobtype,
uniquify_custom=True,
omit_one_from_standard=False,
name_list=None):
"""
Uniquify a job name by adding a trailing integer to ensure that there are no
conflicting file names in the current working directory. Standard job names
will always have an integer added (e.g. jobname_1, jobname_2, jobname_3).
Modified job names (and custom job names if `uniquify_custom` is True) will
only have an integer added if necessary (e.g. jobname, jobname_2,
jobname_3 - note that there is no "_1").
:param jobname: The job name to uniquify
:type jobname: basestring
:param jobtype: The job name type, which will determine the uniquifying
behavior
:type jobtype: `JobnameType`
:param uniquify_custom: Whether we should uniquify custom job name by adding
integers to the end. If False, only standard and modified job names will be
uniquified.
:type uniquify_custom: bool
:param omit_one_from_standard: If True, standard job names will only have an
integer added if necessary (i.e. <jobname> instead of <jobname>_1)
:type omit_one_from_standard: bool
:param name_list: Optional list of names to uniquify against. If not given,
the name will be compared against the directory listing of the CWD
:type name_list: list of basestring
:return: The uniquified job name
:rtype: basestring
"""
if jobtype is JobnameType.Standard:
jobname = get_next_jobname(jobname,
omit_one_from_standard,
name_list=name_list)
elif jobtype is JobnameType.Modified or uniquify_custom:
jobname = get_next_jobname(jobname, True, name_list=name_list)
return jobname
[docs]def determine_jobtype(current_jobname,
old_standard_jobname,
new_standard_jobname=None,
trim_custom=False):
"""
Classify the current job name type and generate the new non-unique job name.
Note that, as a special case, blank job names will be classified as - and
replaced with - the standard job name.
:param current_jobname: The job name to update and classify
:type current_jobname: basestring
:param old_standard_jobname: The standard job name used for classifying the
current job name
:type old_standard_jobname: basestring
:param new_standard_jobname: The standard job name used for generating the
new job name. If not given, `old_standard_jobname` will be used.
:type new_standard_jobname: basestring
:param trim_custom: If True, any trailing uniquifying integer will be
removed from custom job names. If False, custom job names will not be
modified.
:type trim_custom: bool
:return: A tuple of
- The new non-unique job name (basestring)
- The job name type (`JobnameType`)
:rtype: tuple
"""
if new_standard_jobname is None:
new_standard_jobname = old_standard_jobname
if not current_jobname:
return new_standard_jobname, JobnameType.Standard
jobname_re = MOD_JOBNAME_RE % re.escape(old_standard_jobname)
# If incrementing the jobname, strip off any appended digits
if _is_increment_job_name_set():
jobname_re += MOD_JOBNAME_RE_UNIQUIFY
jobname_re += "$"
match = re.match(jobname_re, current_jobname, re.IGNORECASE | re.VERBOSE)
pre, std, post, custom = match.groups()
if std and not (pre or post):
return new_standard_jobname, JobnameType.Standard
elif std:
jobname = pre + new_standard_jobname + post
return jobname, JobnameType.Modified
elif trim_custom:
return custom, JobnameType.Custom
else:
return current_jobname, JobnameType.Custom
[docs]def update_jobname(current_jobname,
standard_jobname,
uniquify_custom=True,
name_list=None):
"""
Update the job name by adding new uniquifying digits at the end
:param current_jobname: The job name to update
:type current_jobname: basestring
:param standard_jobname: The standard job name for the panel
:type old_standard_jobname: basestring
:param uniquify_custom: Whether we should uniquify custom job name by adding
integers to the end. If False, only standard and modified job names will be
uniquified.
:type uniquify_custom: bool
:param name_list: Optional list of names to uniquify against. If not given,
the name will be compared against the directory listing of the CWD
:type name_list: list of basestring
:return: The updated job name
:rtype: basestring
"""
new_jobname, jobtype = determine_jobtype(current_jobname,
standard_jobname,
trim_custom=uniquify_custom)
return uniquify(new_jobname, jobtype, uniquify_custom, name_list=name_list)
[docs]def get_next_jobname(jobname, omit_one=False, name_list=None):
"""
Given a job name, choose the next available unique job name based on the
names of existing files in the current working directory
:param jobname: The job name to uniquify
:type jobname: basestring
:param omit_one: If `jobname` is unique by itself, should we omit
appending the "_1"
:type omit_one: bool
:param name_list: Optional list of names to uniquify against. If not given,
the name will be compared against the directory listing of the CWD
:type name_list: list of basestring
:return: The new job name
:rtype: basestring
:note: This method will not return <jobname>_1 if a <jobname> file exists.
Instead, it will return <jobname>_2 (or whatever the next available integer
is). In other words, <jobname> and <jobname>_1 are assumed to be
equivalent.
"""
if not _is_increment_job_name_set():
return jobname
jobname_re = re.escape(jobname) + r"""
(?:_([\d]+))? # A trailing underscore and uniquifying digits
(?:\..*)?$ # An optional period and file extension
"""
if name_list is None:
name_list = os.listdir(".")
digits = [0]
for cur_name in name_list:
match = re.match(jobname_re, cur_name, re.IGNORECASE | re.VERBOSE)
if match:
cur_digit = match.group(1)
if cur_digit is not None:
cur_digit = int(cur_digit)
else:
cur_digit = 1
digits.append(cur_digit)
suffix = max(digits) + 1
if suffix == 1 and omit_one:
return jobname
else:
return "%s_%i" % (jobname, suffix)