Compare commits

...

3 commits

Author SHA1 Message Date
18508fd549 Code cleanups 2019-02-05 14:29:33 +01:00
4ba2eb66a0 Add missing appdirs dependency 2019-02-05 13:39:56 +01:00
71afacbbcb Fix infinite loop when not logged in. 2019-02-05 13:39:50 +01:00
7 changed files with 44 additions and 46 deletions

View file

@ -44,10 +44,11 @@ def get_story_transitions(token: str, project_id: int,
after: Optional[datetime] = None,
before: Optional[datetime] = None) \
-> List[Dict[str, Any]]:
parameters = {
'occurred_after': after.isoformat() if after else None,
'occurred_before': before.isoformat() if before else None,
}
parameters = {}
if after:
parameters['occurred_after'] = after.isoformat()
if before:
parameters['occurred_before'] = before.isoformat()
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}'

View file

@ -1,5 +1,5 @@
import api.projects
from typing import Dict, Any
from typing import Dict, Any, Optional
from color import Color
COLOR_TITLE = Color(Color.YELLOW)
@ -14,15 +14,15 @@ COLOR_ACCEPTED = Color(Color.BRIGHT_GREEN)
COLOR_REJECTED = Color(Color.BRIGHT_RED)
def _format_state(state: str) -> str:
def _format_state(state: str, header: Optional[bool] = False) -> str:
STATES = {
'accepted': COLOR_ACCEPTED.format('accepted'),
'rejected': COLOR_REJECTED.format('rejected'),
'delivered': COLOR_DELIVERED.format('delivered'),
'finished': COLOR_FINISHED.format('finished'),
'started': COLOR_STARTED.format('started'),
'planned': COLOR_PLANNED.format('planned'),
'unstarted': COLOR_PLANNED.format('unstarted'),
'accepted': COLOR_ACCEPTED.format('Acc' if header else 'accepted'),
'rejected': COLOR_REJECTED.format('Rej' if header else 'rejected'),
'delivered': COLOR_DELIVERED.format('Del' if header else 'delivered'),
'finished': COLOR_FINISHED.format('Fin' if header else 'finished'),
'started': COLOR_STARTED.format('Sta' if header else 'started'),
'planned': COLOR_PLANNED.format('Pln' if header else 'planned'),
'unstarted': COLOR_PLANNED.format('Uns' if header else 'unstarted'),
}
return STATES[state]

View file

@ -133,13 +133,13 @@ def __print_blockers(token: str, project_id: int, story_id: int) -> None:
print(f' [{resolved}] {desc}')
def stories_info(story: str) -> None:
def stories_info(story_b32: str) -> None:
try:
token = Config['user']['api_token']
except KeyError:
sys.exit(1)
story_id = base32.decode(story)
story_id = base32.decode(story_b32)
story = api.stories.get(token, story_id)
project_id = story['project_id']

View file

@ -30,7 +30,7 @@ def projects(context: click.Context) -> None:
table = []
for project in sorted(projects_, key=lambda project_: project_['name']):
code = base32.encode(project['id'])
alias_ = aliases.get(project['id'])
alias_ = aliases.get(project['id'], '')
table.append((code, project['name'], alias_))
print(tabulate.tabulate(table, headers=('Code', 'Name', 'Alias')))

View file

@ -13,8 +13,8 @@ from config import Config
from util import require_login
from . import COLOR_HEADER, COLOR_PLANNED, COLOR_STARTED, COLOR_FINISHED, \
COLOR_DELIVERED, COLOR_ACCEPTED, COLOR_REJECTED, _format_state, \
_get_persons
COLOR_DELIVERED, COLOR_ACCEPTED, COLOR_REJECTED, _format_state, \
_get_persons
from ._stories_info import stories_info
from .cli import cli
@ -26,8 +26,8 @@ Persons = Dict[int, Dict[str, Any]]
Totals = DefaultDict[int, Dict[str, int]]
def __ceil(value: float) -> int:
return int(math.ceil(value))
def __burndown(color, letter, points):
return color.format(letter * int(math.ceil(points)))
def __get_row(item: Tuple[int, Dict[str, int]], persons: Persons,
@ -37,17 +37,17 @@ def __get_row(item: Tuple[int, Dict[str, int]], persons: Persons,
estimates = [points[state] for state in STATES]
progress = '[' + \
COLOR_PLANNED.format('P' * __ceil(points['planned'])) + \
COLOR_REJECTED.format('R' * __ceil(points['rejected'])) + \
COLOR_STARTED.format('S' * __ceil(points['started'])) + \
COLOR_FINISHED.format('F' * __ceil(points['finished'])) + \
COLOR_DELIVERED.format('D' * __ceil(points['delivered']))
progress = '['
if show_accepted:
progress += COLOR_ACCEPTED.format('A' * __ceil(points['accepted']))
progress += __burndown(COLOR_ACCEPTED, 'A', points['accepted'])
progress += ']'
progress += \
__burndown(COLOR_DELIVERED, 'D', points['delivered']) + \
__burndown(COLOR_FINISHED, 'F', points['finished']) + \
__burndown(COLOR_STARTED, 'S', points['started']) + \
__burndown(COLOR_REJECTED, 'R', points['rejected']) + \
__burndown(COLOR_PLANNED, 'P', points['planned']) + \
']'
return name, (*estimates), sum(estimates), progress
@ -96,7 +96,7 @@ def __print_totals(totals: Totals, persons: Persons, show_accepted: bool) \
-> None:
COLOR_HEADER.print('Point totals:', end='\n\n')
state_headers = [_format_state(state) for state in STATES]
state_headers = [_format_state(state, header=True) for state in STATES]
headers = ('Owner', *state_headers, 'Total', 'Progress')
data = sorted((__get_row(item, persons, show_accepted)
@ -128,15 +128,15 @@ def __print_burndown(token: str, iteration: Dict[str, Any],
continue
progress = ''
if show_accepted:
progress += '\033[92m' + 'A' * round(counts[0] - accepted_points)
progress += '\033[38;5;208m' + 'D' * round(counts[1])
progress += '\033[94m' + 'F' * round(counts[2])
progress += '\033[38;5;226m' + 'S' * round(counts[3])
progress += '\033[90m' + 'P' * round(counts[5])
progress += '\033[0m'
progress += __burndown(COLOR_ACCEPTED, 'A',
counts[0] - accepted_points)
progress += \
__burndown(COLOR_DELIVERED, 'D', counts[1]) + \
__burndown(COLOR_FINISHED, 'F', counts[2]) + \
__burndown(COLOR_STARTED, 'S', counts[3]) + \
__burndown(COLOR_PLANNED, 'P', counts[5]) + \
__burndown(COLOR_PLANNED, 'U', counts[6])
print(f'{date}: {progress}')
@ -174,14 +174,8 @@ def _set_story_state(story: str, state: str) -> None:
api.stories.put_story(token, story_id, current_state=state)
def _complete_projects(ctx: click.Context, args: List[str], incomplete: str) \
-> List[str]:
return [alias for alias in Config['project_aliases']
if alias.startswith(incomplete)]
@cli.command('stories')
@click.argument(
'project', type=click.STRING, autocompletion=_complete_projects)
@click.argument('project', type=click.STRING)
@click.argument('story', required=False)
@click.argument('action', required=False)
@click.option('--scope', default='current')

View file

@ -6,3 +6,4 @@ click-fish==0.1.0
click==7.0
requests==2.18.4
tabulate==0.8.2
appdirs==1.4.3

View file

@ -1,5 +1,6 @@
import shutil
import textwrap
from commands.login import login
from typing import Any, Callable
from config import Config
@ -25,8 +26,9 @@ def print_wrap(text: str, indent: str = '', end: str = '\n') -> None:
def require_login(function: Callable) -> Callable:
def wrapper(*args: Any, **kwargs: Any) -> Any:
while 'api_token' not in Config['user']:
if 'api_token' not in Config['user']:
print('Not logged in. Please use the login command to log in.')
return
return function(*args, **kwargs)