def get_publish_name(self, path, sequence=False): """ Given a file path, return the display name to use for publishing. Typically, this is a name where the path and any version number are removed in order to keep the publish name consistent as subsequent versions are published. Example:: # versioned file. remove the version in: /path/to/the/file/scene.v001.ma out: scene.ma # image sequence. replace the frame number with #s in: /path/to/the/file/my_file.001.jpg out: my_file.###.jpg :param path: The path to a file, likely one to be published. :param sequence: If True, treat the path as a sequence name and replace the frame number with placeholder :return: A publish display name for the provided path. """ publisher = self.parent logger = publisher.logger logger.debug("Getting publish name for path: %s ..." % (path,)) path_info = publisher.util.get_file_path_components(path) filename = path_info["filename"] version_pattern_match = re.search(VERSION_REGEX, filename) frame_pattern_match = re.search(FRAME_REGEX, filename) if version_pattern_match: # found a version number, use the other groups to remove it prefix = version_pattern_match.group(1) if len(version_pattern_match.groups()) == 5: extension = version_pattern_match.group(5) else: extension = version_pattern_match.group(4) or "" if extension: publish_name = "%s.%s" % (prefix, extension) else: publish_name = prefix elif frame_pattern_match and sequence: # found a frame number, meplace it with #s prefix = frame_pattern_match.group(1) frame_sep = frame_pattern_match.group(2) frame = frame_pattern_match.group(3) display_str = "#" * len(frame) extension = frame_pattern_match.group(4) or "" publish_name = "%s%s%s.%s" % (prefix, frame_sep, display_str, extension) else: publish_name = filename logger.debug("Returning publish name: %s" % (publish_name,)) return publish_name
def get_version_number(self, path): """ Extract a version number from the supplied path. This is used by plugins that need to know what version number to associate with the file when publishing. :param path: The path to a file, likely one to be published. :return: An integer representing the version number in the supplied path. If no version found, ``None`` will be returned. """ publisher = self.parent logger = publisher.logger logger.debug("Getting version number for path: %s ..." % (path,)) path_info = publisher.util.get_file_path_components(path) filename = path_info["filename"] # default if no version number detected version_number = None # if there's a version in the filename, extract it version_pattern_match = re.search(VERSION_REGEX, filename) if version_pattern_match: version_number = int(version_pattern_match.group("version")) logger.debug("Returning version number: %s" % (version_number,)) return version_number
def get_publish_name(self, path, sequence=False): """ Given a file path, return the display name to use for publishing. Typically, this is a name where the path and any version number are removed in order to keep the publish name consistent as subsequent versions are published. Example:: # versioned file. remove the version in: /path/to/the/file/scene.v001.ma out: scene.ma # image sequence. replace the frame number with #s in: /path/to/the/file/my_file.001.jpg out: my_file.###.jpg :param path: The path to a file, likely one to be published. :param sequence: If True, treat the path as a sequence name and replace the frame number with placeholder :return: A publish display name for the provided path. """ publisher = self.parent logger = publisher.logger logger.debug("Getting publish name for path: %s ..." % (path,)) path_info = publisher.util.get_file_path_components(path) filename = path_info["filename"] match = re.search(VERSION_REGEX, filename) # frame_pattern_match = re.search(FRAME_REGEX, filename) if match: publish_name = publish_code = match.group("prefix") if match.group("version"): publish_code += match.group("ver_sep") publish_code += "v" + match.group("version") if match.group("rep"): publish_name += match.group("rep_sep") publish_name += match.group("rep") publish_code += match.group("rep_sep") publish_code += match.group("rep") if match.group("frame"): display_str = "#" * len(match.group("frame")) publish_name += match.group("frame_sep") publish_name += display_str publish_code += match.group("frame_sep") publish_code += display_str publish_name += "." + match.group("ext") publish_code += "." + match.group("ext") else: publish_name = filename publish_code = filename logger.debug("Returning publish name, publish code: {},{}".format(publish_name, publish_code)) return publish_name, publish_code
def _definition_variations(self, definition): """ Determines all possible definition based on combinations of optional sectionals. "{foo}" ==> ['{foo}'] "{foo}_{bar}" ==> ['{foo}_{bar}'] "{foo}[_{bar}]" ==> ['{foo}', '{foo}_{bar}'] "{foo}_[{bar}_{baz}]" ==> ['{foo}_', '{foo}_{bar}_{baz}'] """ # split definition by optional sections tokens = re.split(r"(\[[^]]*\])", definition) # seed with empty string definitions = [""] for token in tokens: temp_definitions = [] # regex return some blank strings, skip them if token == "": continue if token.startswith("["): # check that optional contains a key if not re.search("{*%s}" % constants.TEMPLATE_KEY_NAME_REGEX, token): raise TankError( 'Optional sections must include a key definition. Token: "%s" Template: %s' % (token, self)) # Add definitions skipping this optional value temp_definitions = definitions[:] # strip brackets from token token = re.sub(r"[\[\]]", "", token) # check non-optional contains no dangleing brackets if re.search(r"[\[\]]", token): raise TankError( "Square brackets are not allowed outside of optional section definitions." ) # make defintions with token appended for definition in definitions: temp_definitions.append(definition + token) definitions = temp_definitions return definitions
def get_next_version_path(self, path): """ Given a file path, return a path to the next version. This is typically used by auto-versioning logic in plugins that need to save the current work file to the next version number. If no version can be identified in the supplied path, ``None`` will be returned, indicating that the next version path can't be determined. :param path: The path to a file, likely one to be published. :return: The path to the next version of the supplied path. """ publisher = self.parent logger = publisher.logger logger.debug("Getting next version of path: %s ..." % (path, )) # default next_version_path = None path_info = publisher.util.get_file_path_components(path) filename = path_info["filename"] # see if there's a version in the supplied path version_pattern_match = re.search(VERSION_REGEX, filename) if version_pattern_match: prefix = version_pattern_match.group(1) version_sep = version_pattern_match.group(2) version_str = version_pattern_match.group(3) extension = version_pattern_match.group(4) or "" # make sure we maintain the same padding padding = len(version_str) # bump the version number next_version_number = int(version_str) + 1 # create a new version string filled with the appropriate 0 padding next_version_str = "v%s" % ( str(next_version_number).zfill(padding)) new_filename = "%s%s%s" % (prefix, version_sep, next_version_str) if extension: new_filename = "%s.%s" % (new_filename, extension) # build the new path in the same folder next_version_path = os.path.join(path_info["folder"], new_filename) logger.debug("Returning next version path: %s" % (next_version_path, )) return next_version_path
def test_unicode_override(self): """ Ensure that the unicode flag overrides the flag insertion behavior. """ char = u"a漢字" expr = r"a\w+" # test all wrapped methods self.assertTrue(bool(sgre.compile(expr, flags=re.U).match(char))) self.assertEqual(len(sgre.findall(expr, char, flags=re.U)), 1) self.assertTrue(bool(sgre.match(expr, char, flags=re.U))) self.assertTrue(bool(sgre.search(expr, char, flags=re.U))) self.assertEqual(len(sgre.split(expr, "$ %s @" % char, flags=re.U)), 2) self.assertEqual(sgre.sub(expr, "@", char, flags=re.U), "@")
def test_wrap(self): r""" Ensure that sgre injects the re.ASCII flag appropriately, and that unicode characters do not match `\w` in Python 2 or 3. """ char = u"漢字" expr = r"\w+" # test all wrapped methods self.assertFalse(bool(sgre.compile(expr).match(char))) self.assertEqual(len(sgre.findall(expr, char)), 0) self.assertFalse(bool(sgre.match(expr, char))) self.assertFalse(bool(sgre.search(expr, char))) self.assertEqual(len(sgre.split(expr, "$ %s @" % char)), 1) self.assertEqual(sgre.sub(expr, "@", char), char)
def test_wrap_kwarg(self): r""" Ensure that sgre injects the re.ASCII flag appropriately when flags are also passed as keyword arguments, and that unicode characters do not match `\w` in Python 2 or 3. """ char = u"a漢字" expr = r"a\w+" # test all wrapped methods self.assertFalse(bool(sgre.compile(expr, flags=re.I).match(char))) self.assertEqual(len(sgre.findall(expr, char, flags=re.I)), 0) self.assertFalse(bool(sgre.match(expr, char, flags=re.I))) self.assertFalse(bool(sgre.search(expr, char, flags=re.I))) self.assertEqual(len(sgre.split(expr, "$ %s @" % char, flags=re.I)), 1) self.assertEqual(sgre.sub(expr, "@", char, flags=re.I), char)
def _validate_manifest(source_path): """ Validate that the manifest file is present and valid. :param source_path: Source path to plugin :return: parsed yaml content of manifest file """ # check for source manifest file manifest_path = os.path.join(source_path, "info.yml") if not os.path.exists(manifest_path): raise TankError("Cannot find plugin manifest '%s'" % manifest_path) logger.debug("Reading %s" % manifest_path) try: with open(manifest_path, "rt") as fh: manifest_data = yaml.load(fh) except Exception as e: raise TankError("Cannot parse info.yml manifest: %s" % e) logger.debug("Validating manifest...") # legacy check - if we find entry_point, convert it across # to be plugin_id if "entry_point" in manifest_data: logger.warning( "Found legacy entry_point syntax. Please upgrade to use plugin_id instead." ) manifest_data["plugin_id"] = manifest_data["entry_point"] for parameter in REQUIRED_MANIFEST_PARAMETERS: if parameter not in manifest_data: raise TankError( "Required plugin manifest parameter '%s' missing in '%s'" % (parameter, manifest_path)) # plugin_id needs to be alpha numeric + period if re.search(r"^[a-zA-Z0-9_\.]+$", manifest_data["plugin_id"]) is None: raise TankError( "Plugin id can only contain alphanumerics, period and underscore characters." ) return manifest_data
def get_frame_sequence_path(self, path, frame_spec=None): """ Given a path with a frame number, return the sequence path where the frame number is replaced with a given frame specification such as ``{FRAME}`` or ``%04d`` or ``$F``. :param path: The input path with a frame number :param frame_spec: The frame specification to replace the frame number with. :return: The full frame sequence path """ publisher = self.parent path_info = publisher.util.get_file_path_components(path) # see if there is a frame number frame_pattern_match = re.search(VERSION_REGEX, path_info["filename"]) if not frame_pattern_match or not frame_pattern_match.group("frame"): # no frame number detected. carry on. return None prefix = frame_pattern_match.group("pre_frame") frame_sep = frame_pattern_match.group("frame_sep") frame_str = frame_pattern_match.group("frame") extension = frame_pattern_match.group("ext") or "" # make sure we maintain the same padding if not frame_spec: padding = len(frame_str) frame_spec = "%%0%dd" % (padding,) seq_filename = "%s%s%s" % (prefix, frame_sep, frame_spec) if extension: seq_filename = "%s.%s" % (seq_filename, extension) # build the full sequence path return os.path.join(path_info["folder"], seq_filename)
def get_version_path(self, path, version): """ Given a path without a version number, return the path with the supplied version number. If a version number is detected in the supplied path, the path will be returned as-is. :param path: The path to inject a version number. :param version: The version number to inject. :return: The modified path with the supplied version number inserted. """ publisher = self.parent logger = publisher.logger logger.debug("Getting version %s of path: %s ..." % (version, path)) path_info = publisher.util.get_file_path_components(path) filename = path_info["filename"] # see if there's a version in the supplied path version_pattern_match = re.search(VERSION_REGEX, filename) if version_pattern_match and version_pattern_match.group("version"): # version number already in the path. return the original path return path (basename, ext) = os.path.splitext(filename) # construct the new filename with the version number inserted version_filename = "%s.%s%s" % (basename, version, ext) # construct the new, full path version_path = os.path.join(path_info["folder"], version_filename) logger.debug("Returning version path: %s" % (version_path,)) return version_path
def get_frame_sequences(self, folder, extensions=None, frame_spec=None): """ Given a folder, inspect the contained files to find what appear to be files with frame numbers. :param folder: The path to a folder potentially containing a sequence of files. :param extensions: A list of file extensions to retrieve paths for. If not supplied, the extension will be ignored. :param frame_spec: A string to use to represent the frame number in the return sequence path. :return: A list of tuples for each identified frame sequence. The first item in the tuple is a sequence path with the frame number replaced with the supplied frame specification. If no frame spec is supplied, a python string format spec will be returned with the padding found in the file. Example:: get_frame_sequences( "/path/to/the/folder", ["exr", "jpg"], frame_spec="{FRAME}" ) [ ( "/path/to/the/supplied/folder/key_light1.{FRAME}.exr", [<frame_1_path>, <frame_2_path>, ...] ), ( "/path/to/the/supplied/folder/fill_light1.{FRAME}.jpg", [<frame_1_path>, <frame_2_path>, ...] ) ] """ publisher = self.parent logger = publisher.logger logger.debug("Looking for sequences in folder: '%s'..." % (folder,)) # list of already processed file names processed_names = {} # examine the files in the folder for filename in os.listdir(folder): file_path = os.path.join(folder, filename) if os.path.isdir(file_path): # ignore subfolders continue # see if there is a frame number frame_pattern_match = re.search(VERSION_REGEX, filename) if not frame_pattern_match: # no frame number detected. carry on. continue prefix = frame_pattern_match.group("prefix") frame_sep = frame_pattern_match.group("frame_sep") frame_str = frame_pattern_match.group("frame") extension = frame_pattern_match.group("ext") or "" # filename without a frame number. file_no_frame = "%s.%s" % (prefix, extension) if file_no_frame in processed_names: # already processed this sequence. add the file to the list processed_names[file_no_frame]["file_list"].append(file_path) continue if extensions and extension not in extensions: # not one of the extensions supplied continue # make sure we maintain the same padding if not frame_spec: padding = len(frame_str) frame_spec = "%%0%dd" % (padding,) seq_filename = "%s%s%s" % (prefix, frame_sep, frame_spec) if extension: seq_filename = "%s.%s" % (seq_filename, extension) # build the path in the same folder seq_path = os.path.join(folder, seq_filename) # remember each seq path identified and a list of files matching the # seq pattern processed_names[file_no_frame] = { "sequence_path": seq_path, "file_list": [file_path], } # build the final list of sequence paths to return frame_sequences = [] for file_no_frame in processed_names: seq_info = processed_names[file_no_frame] seq_path = seq_info["sequence_path"] logger.debug("Found sequence: %s" % (seq_path,)) frame_sequences.append((seq_path, seq_info["file_list"])) return frame_sequences