[docs]defbuild_folder_name(type:Literal["story","task"],date:str,id:int,sanitized_title:str,)->str:""" Build a folder name for a story or task. Constructs a folder name in the format: ``{type}-{date}-{id}-{sanitized_title}`` :param type: Entity type ("story" or "task") :param date: Creation date in YYYY-MM-DD format :param id: Global ID (will be zero-padded) :param sanitized_title: Pre-sanitized title string :returns: Folder name string """returnf"{type}-{date}-{str(id).zfill(ZERO_PADDING)}-{sanitized_title}"
# Pattern: (story|task)-YYYY-MM-DD-ID-sanitized-title# Groups: (1) type, (2) date, (3) id, (4) title# Note: ID accepts any number of digits for flexibility (5-digit, 6-digit, etc.)# The id is converted to int() after extractionfolder_pattern=re.compile(f"({WordsEnum.story.value}|{WordsEnum.task.value})"+r"-(\d{4}-\d{2}-\d{2})-(\d+)-(.+)$")
[docs]@dataclasses.dataclassclassTicket:type:str# "story" or "task"id:inttitle:strdate:str
[docs]@classmethoddeffrom_folder(cls,folder:Path)->T.Optional["Ticket"]:""" Parse a folder path to extract ticket information. Parses the folder name according to the pattern: ``{type}-{date}-{id}-{title}`` where type is "story" or "task". The encoded title is decoded back to the original title with spaces. :param folder: Path object representing the folder (only name is checked, no filesystem validation is performed) :returns: Ticket instance if the folder name matches the expected pattern, None otherwise """match=folder_pattern.match(folder.name)ifmatchisNone:returnNonetype_,date,id_str,encoded_title=match.groups()returncls(type=type_,id=int(id_str),title=decode_title(encoded_title),date=date,)
[docs]defsafe_write(path:Path,content:str):""" Safely write content to a file, creating parent directories if needed. """try:path.write_text(content,encoding="utf-8")exceptFileNotFoundError:path.parent.mkdir(parents=True,exist_ok=True)path.write_text(content,encoding="utf-8")