import argparse import re import sys from datetime import datetime from typing import Any, Dict import base32_crockford 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], persons: Dict[int, 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'], end='\n\n') if 'current_state' in story: state = _format_state(story['current_state']) COLOR_HEADER.print('State:', state, 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() print_wrap(description, indent=' ', end='\n\n') 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(token: str, project_id: int, story_id: int) -> None: """Prints the tasks of the story, if available.""" tasks = api.stories.get_tasks(token, 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_crockford.encode(task['id']), end=':\033[0m ') print(task['description']) print() def __print_comments(token: str, 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(token, project_id, story_id) if comments: COLOR_HEADER.print('Comments:') for comment in comments: text = comment['text'].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(token: str, project_id: int, story_id: int) -> None: """Prints the stories that block this story, if available.""" blockers = api.stories.get_blockers(token, project_id, story_id) if blockers: COLOR_HEADER.print('Blockers:') def blocker_repl(matchgroup: Any) -> str: id = int(matchgroup.group(1)) code = base32_crockford.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(args: argparse.Namespace) -> None: try: token = Config['user']['api_token'] except KeyError: sys.exit(1) story_id = base32_crockford.decode(args.story) story = api.stories.get(token, story_id) project_id = story['project_id'] persons = _get_persons(token, project_id) __print_story(story, persons) __print_owners(story, persons) __print_description(story) __print_labels(story) __print_tasks(token, project_id, story_id) __print_comments(token, project_id, story_id, persons) __print_blockers(token, project_id, story_id)