feat: regex implementation

This commit is contained in:
2025-10-04 20:17:43 +01:00
parent 66c6540294
commit f8814ce993
8 changed files with 178 additions and 71 deletions

View File

@@ -1,5 +0,0 @@
FROM python:3.11-alpine
COPY example-files .
CMD ["ls", "/shows"]

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env bash
# builds a container with the current Dockerfile for testing the bb-asset-validation program
docker build -t bb-asset-validation .

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env bash
# run the docker container for testing the bb-asset-validation program
docker run bb-asset-validation

View File

@@ -0,0 +1,9 @@
# should throw invalid version error
/shows/projectX/assets/environment/forest_v003/model/render/forest_beauty_v002.1001.exr
# should throw wrong size error
/shows/projectX/assets/environment/forest_v003/model/forest_beauty_v002.1001.exr
# should throw invalid prefix path error
/other/projectX/assets/environment/forest_v002/model/cache/forest_v002.fbx

22
example-paths-mixed.txt Normal file
View File

@@ -0,0 +1,22 @@
# valid paths
/shows/projectX/assets/environment/forest_v001/model/render/forest_beauty_v001.1001.exr
/shows/projectX/assets/environment/forest_v001/model/render/forest_beauty_v001.1002.exr
/shows/projectX/assets/environment/forest_v001/model/render/forest_beauty_v001.1003.exr
/shows/projectX/assets/environment/forest_v001/model/cache/forest_v001.fbx
/shows/projectX/assets/environment/forest_v002/model/render/forest_beauty_v002.1001.exr
/shows/projectX/assets/environment/forest_v002/model/render/forest_beauty_v002.1002.exr
/shows/projectX/assets/environment/forest_v002/model/render/forest_beauty_v002.1003.exr
/shows/projectX/assets/environment/forest_v002/model/cache/forest_v002.fbx
# should throw invalid version error
/shows/projectX/assets/environment/forest_v003/model/render/forest_beauty_v002.1001.exr
# should throw wrong size error
/shows/projectX/assets/environment/forest_v003/model/forest_beauty_v002.1001.exr
# valid path
/shows/projectX/assets/environment/forest_v002/model/cache/forest_v002.fbx
# should throw invalid prefix path error
/other/projectX/assets/environment/forest_v002/model/cache/forest_v002.fbx

View File

@@ -1,6 +1,14 @@
import argparse import argparse
import os.path import os.path
import sys import sys
import re
# Global Variables. In production code I would not use global variables
# ANSI color escape codes, will not work on windows.
ANSI_FORE_RED = "\033[31m"
ANSI_FORE_GREEN = "\033[32m"
ANSI_DEFAULT = "\033[0m"
def parse_args() -> argparse.Namespace: def parse_args() -> argparse.Namespace:
""" """
@@ -21,21 +29,6 @@ def parse_args() -> argparse.Namespace:
return parser.parse_args() return parser.parse_args()
def validate_shows_dir(shows_directory: str):
"""
Checks the given show directory is valid.
Arguments:
shows_directory (str): The directory shows are stored within
Returns:
bool: Whether the directory is valid.
"""
# Check path exists
if(not os.path.exists(shows_directory)):
raise ValueError("Designated show path does not exist: " + shows_directory)
def split_path(path): def split_path(path):
PATH_DELIMETER = os.path.sep PATH_DELIMETER = os.path.sep
@@ -46,66 +39,162 @@ def split_path(path):
return split_path return split_path
def validate_asset_path_structure(parsed_args, asset_path: str): def get_path_components(asset_path: str):
pattern = re.compile(r"(?P<full_path>/shows/(?P<project_name>\S*?)/\S*?/(?P<asset_type>\S*?)/(?P<asset_name>\S*?)_v(?P<asset_version>\S*?)/(?P<task>\S*?)/\S*?/(?P<file_name>\S*?)_v(?P<file_version>\d*)\D*?(?P<frame_number>\d*)\D*?\.(?P<file_extension>\w*)\Z)")
match = pattern.match(asset_path)
path_components = match.groupdict()
return path_components
def assemble_new_path(path_components: dict) -> str:
frame_number = f".{path_components['frame_number']}" if path_components['frame_number'] else ""
file_extension = f".{path_components['file_extension']}" if path_components['file_extension'] else ""
return f"/shows/{path_components['project_name']}/staging/delivery/assets/{path_components['asset_type']}_{path_components['asset_name']}/{path_components['task']}_{path_components['asset_version']}/{path_components['file_name']}{frame_number}{file_extension}"
def should_skip_line(line):
line = line.strip()
return (
len(line)<=1 or # too short
line[0]=="#" # comment
)
def validate_paths_file(parsed_args):
with open(parsed_args.paths_file, "r") as f:
line = f.readline()
while(line):
line = f.readline()
if(should_skip_line(line)):
continue
# clean line
path = line.strip()
# validate path
if(not validate_path(parsed_args, path)):
continue
# all components of the project
all_components = get_path_components(path)
# map the specific components listed in the brief
stored_components = map_stored_components(all_components)
# assemble the new path
new_path = assemble_new_path(all_components)
# print out the resulting values
print_result(path, new_path, stored_components)
def validate_static_list(parsed_args):
static_list = [
"/shows/projectX/assets/environment/forest_v001/model/render/forest_beauty_v001.1002.exr",
"/shows/projectX/assets/environment/forest_v001/model/cache/forest_v001.fbx"
]
for line in static_list:
if(should_skip_line(line)):
continue
# clean line
path = line.strip()
# validate path
if(not validate_path(parsed_args, path)):
continue
# all components of the project
all_components = get_path_components(path)
# map the specific components listed in the brief
stored_components = map_stored_components(all_components)
# assemble the new path
new_path = assemble_new_path(all_components)
# print out the resulting values
print_result(path, new_path, stored_components)
def map_stored_components(all_components):
stored_components = {
"full_path" : all_components["full_path"],
"project" : all_components["project_name"],
"asset_type" : all_components["asset_type"],
"asset_name" : all_components["asset_name"],
"version" : all_components["asset_version"],
"task" : all_components["task"],
"file_extension" :all_components["file_extension"],
}
return stored_components
def print_result(original_path, new_path, stored_components):
print(f"{ANSI_FORE_GREEN}\n--------------")
print(f"original path:\n\t{original_path}\n")
print(f"new path:\n\t{new_path}\n")
print(f"stored components:\n\t{stored_components}")
print(f"--------------\n{ANSI_DEFAULT}")
# def validate_static_list():
def throw_schema_error(path: str, error_message: str) -> None:
print(f"\n{ANSI_FORE_RED}-----\nPath is invalid: {path}\n{error_message}\n-----{ANSI_DEFAULT}", file=sys.stderr)
def validate_path(parsed_args: argparse.Namespace, asset_path: str) -> bool:
"""
Validates the given path based on the arguments provided.
Parameters:
parsed_args (argparse.Namespace): CLI arguments
asset_path (str): The path to validate
Returns:
Whether the provided path is valid
"""
# check path is absolute
if(asset_path[0] != "/"): if(asset_path[0] != "/"):
print(f"\nError for path: {asset_path}\n-------------", file=sys.stderr) throw_schema_error(asset_path, "Expected absolute asset path, recieved path does not start with '/'")
print("Expected absolute asset path, recieved path does not start with '/'\n", file=sys.stderr) return False
split_asset_path = split_path(os.path.normpath(asset_path))
# check length
EXPECTED_LENGTH=8
split_asset_path = split_path(asset_path)
if(EXPECTED_LENGTH is not None and len(split_asset_path) != EXPECTED_LENGTH):
throw_schema_error(asset_path, f"Path isn't expected length {EXPECTED_LENGTH}.")
return False
# check path is prefixed with shows path # check path is prefixed with shows path
# valid_show_path = True shows_path = os.path.realpath(parsed_args.shows_directory)
split_shows_path = split_path(os.path.realpath(parsed_args.shows_directory)) valid_show_path = shows_path == asset_path[:len(shows_path)]
valid_show_path = split_shows_path == split_asset_path[:len(split_shows_path)]
if(not valid_show_path): if(not valid_show_path):
print(f"\nError for path: {asset_path}\n-------------", file=sys.stderr) throw_schema_error(asset_path, f"Path does not start with expected show directory '{parsed_args.shows_directory}'")
print(f"Path starting with '{split_asset_path[0]}' does not start with expected show directory '{parsed_args.shows_directory}'\n", file=sys.stderr) return False
else:
print("\n--------------\nGOOD PATH:", asset_path,"\n----------------\n")
split_asset_path_suffix = split_asset_path[len(split_shows_path):] # get components
path_components = get_path_components(asset_path)
path_components = {
"project_name" : split_asset_path_suffix[0],
"asset_type" : split_asset_path_suffix[2],
"asset_name" : split_asset_path_suffix[3].split("_")[0],
"task" : split_asset_path_suffix[4],
"version" : split_asset_path_suffix[3].split("_")[1],
"filename" : os.path.splitext(split_asset_path_suffix[6])[0].split("_")[0],
"file_extension" : os.path.splitext(split_asset_path_suffix[6])[1],
}
print(path_components)
new_path = f"/shows/{path_components['project_name']}/staging/delivery/assets/{path_components['asset_type']}_{path_components['asset_name']}/{path_components['task']}_{path_components['version']}/{path_components['filename']}{path_components['file_extension']}"
print("new path:\n", new_path)
# validate versions
if(path_components["asset_version"] != path_components["file_version"]):
throw_schema_error(asset_path, "Asset version and file version does not match.")
return False
return True
def main() -> None: def main() -> None:
# Parse args # Parse args
parsed_args = parse_args() parsed_args = parse_args()
shows_directory = parsed_args.shows_directory
# validate_shows_dir(shows_directory)
if(parsed_args.paths_file): if(parsed_args.paths_file):
with open(parsed_args.paths_file, "r") as f: validate_paths_file(parsed_args)
line = f.readline() else:
while(line): print("No file provided.\n")
line = f.readline() print("Try passing one of the example files with the --paths-file argument.")
print(f"eg. '{ANSI_FORE_GREEN}bb-asset-validation --paths-file example-paths-mixed.txt{ANSI_DEFAULT}'")
print("\nAlternatively, here is the validation of a hardcoded list of paths:")
# clean line validate_static_list(parsed_args)
line = line.strip()
# skip empty lines
if(line==""):
continue
print('line:', line)
validate_asset_path_structure(parsed_args, line)
if __name__ == "__main__": if __name__ == "__main__":