import re import sys from datetime import datetime from typing import Any, Dict from consolemd import Renderer import base32_crockford as base32 import api.stories from config import Config from util import print_wrap from . import COLOR_HEADER, COLOR_TITLE, COLOR_WHITE, _format_state, _get_persons def __print_story(story: Dict[str, Any]) -> None: """ Prints the title, the current state and the estimate of the story, if available. TODO: Split up in functions. """ COLOR_TITLE.print(story["name"]) print(story["url"], end="\n\n") if "current_state" in story: state = _format_state(story["current_state"]) COLOR_HEADER.print("State:", state, end="") if story["current_state"] == "accepted": print(f" (at {story['accepted_at']})", end="") print(end="\n\n") if "estimate" in story: COLOR_HEADER.print("Estimate: ", end="") print(story["estimate"], "points", end="") if len(story.get("owner_ids", [])) > 1: points = story["estimate"] / len(story["owner_ids"]) print(f" ({points} each)", end="") print(end="\n\n") def __print_owners(story: Dict[str, Any], persons: Dict[int, Any]) -> None: """Prints the owners of the story, if available.""" if story.get("owner_ids"): COLOR_HEADER.print("Owners:") owners = [] for owner_id in story["owner_ids"]: name = persons[owner_id]["name"] initials = persons[owner_id]["initials"] owners.append(f" - {name} ({initials})") print("\n".join(owners), end="\n\n") def __print_description(story: Dict[str, Any]) -> None: """Prints the description of the story, if available.""" COLOR_HEADER.print("Description:") if "description" in story: description = story["description"].strip() Renderer().render(description, width=80) print() else: print(" (No description)", end="\n\n") def __print_labels(story: Dict[str, Any]) -> None: """Prints the labels of the story, if available.""" if not story.get("labels"): return COLOR_HEADER.print("Labels:") template = "\033[97;48;5;22m {} \033[0m" labels = " ".join(template.format(label["name"]) for label in story["labels"]) print(f" {labels}", end="\n\n") def __print_tasks(project_id: int, story_id: int) -> None: """Prints the tasks of the story, if available.""" tasks = api.stories.get_tasks(project_id, story_id) if tasks: COLOR_HEADER.print("Tasks:") for task in tasks: print(end=" ") print("[X]" if task["complete"] else "[ ]", end=" \033[34m") print(base32.encode(task["id"]), end=":\033[0m ") print(task["description"]) print() def __print_comments( project_id: int, story_id: int, persons: Dict[int, Dict[str, Any]] ) -> None: """Prints the comments on the story, if available.""" comments = api.stories.get_comments(project_id, story_id) if comments: COLOR_HEADER.print("Comments:") for comment in comments: text = comment.get("text", "[Empty comment]").strip() print_wrap(text, indent=" ") person_id = comment["person_id"] name = persons[person_id]["name"] COLOR_WHITE.print(" -", name, end=" ") date = datetime.strptime(comment["created_at"], "%Y-%m-%dT%H:%M:%SZ") date_str = date.strftime("on %a %Y-%m-%d at %H:%M") print(date_str, end="\n\n") def __print_blockers(project_id: int, story_id: int) -> None: """Prints the stories that block this story, if available.""" blockers = api.stories.get_blockers(project_id, story_id) if blockers: COLOR_HEADER.print("Blockers:") def blocker_repl(matchgroup: Any) -> str: id = int(matchgroup.group(1)) code = base32.encode(id) return COLOR_HEADER.format(code) pattern = re.compile(r"#(\d+)") for blocker in blockers: resolved = "X" if blocker["resolved"] else " " desc = pattern.sub(blocker_repl, blocker["description"]) print(f" [{resolved}] {desc}") def stories_info(story_b32: str) -> None: story_id = base32.decode(story_b32) story = api.stories.get(story_id) project_id = story["project_id"] persons = _get_persons(project_id) __print_story(story) __print_owners(story, persons) __print_description(story) __print_labels(story) __print_tasks(project_id, story_id) __print_comments(project_id, story_id, persons) __print_blockers(project_id, story_id)