Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎨 Use Ruff Formatter #42

Merged
merged 3 commits into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/cicd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ jobs:
- name: 📦 Install dependencies
run: pip install -r requirements-dev.txt # Replace with your requirements file

- name: ❄️ Run Flake8
run: flake8 .
- name: 💻 Run Ruff checks
run: ruff check .

- name: 🧪 Run unit tests
run: pytest --cov
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<a href="LICENSE">
<img src="https://img.shields.io/github/license/jossmoff/bookshelf.svg">
</a>

<a href="https://github.com/astral-sh/ruff">
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;">
</a>
</p>

## 🔎 Tracking your productivity has never been easier
Expand Down
121 changes: 71 additions & 50 deletions bookshelf/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
from bookshelf.console import BookshelfConsole
from bookshelf.storage import BookshelfStorage
from bookshelf.models import Chapter, Story
from bookshelf.exceptions import StoryAlreadyExistsException, StoryNotFoundException, ChapterInProgressException, \
StoryAlreadyFinishedException, ChapterNotInProgressException
from bookshelf.exceptions import (
StoryAlreadyExistsException,
StoryNotFoundException,
ChapterInProgressException,
StoryAlreadyFinishedException,
ChapterNotInProgressException,
)
from bookshelf.param_types import StoryType

bookshelf_storage = BookshelfStorage()
Expand All @@ -18,7 +23,7 @@ def entry_point():
try:
bookshelf()
except (Exception, KeyboardInterrupt) as exception:
bookshelf_console.print(exception.terminal_message, style='red')
bookshelf_console.print(exception.terminal_message, style="red")
exit(0)


Expand All @@ -27,28 +32,34 @@ def bookshelf():
"""📚 Bookshelf - A CLI tool to tracking your stories in the SDLC"""


@bookshelf.command(name='create')
@click.argument('story_name', type=story_type)
@click.option('-f',
'--force',
type=str,
is_flag=True,
help='Usually, the command will not start a story that already exists. '
'This flag disables these checks, use it with care.')
@click.option('-t',
'--tags',
type=str,
help='Comma-separated list of tags that you want to apply to the story.')
@click.option('--start-chapter',
type=bool,
is_flag=True,
show_default=True,
default=False,
help='Optionally start the first chapter of the story you are creating right away.')
@bookshelf.command(name="create")
@click.argument("story_name", type=story_type)
@click.option(
"-f",
"--force",
type=str,
is_flag=True,
help="Usually, the command will not start a story that already exists. "
"This flag disables these checks, use it with care.",
)
@click.option(
"-t",
"--tags",
type=str,
help="Comma-separated list of tags that you want to apply to the story.",
)
@click.option(
"--start-chapter",
type=bool,
is_flag=True,
show_default=True,
default=False,
help="Optionally start the first chapter of the story you are creating right away.",
)
def create_story_entry(story_name: str, force: bool, tags: str, start_chapter: bool):
"""Create a new story for your bookshelf"""
try:
tags_list = [tag.strip() for tag in tags.split(',')] if tags is not None else []
tags_list = [tag.strip() for tag in tags.split(",")] if tags is not None else []

if bookshelf_storage.story_exists(story_name) and not force:
raise StoryAlreadyExistsException(story_name)
Expand All @@ -61,11 +72,11 @@ def create_story_entry(story_name: str, force: bool, tags: str, start_chapter: b
bookshelf_storage.save_story(story)
bookshelf_console.render_story_panel(story)
except KeyboardInterrupt:
bookshelf_console.print(f'📕 Story \'{story_name}\' has been created!')
bookshelf_console.print(f"📕 Story '{story_name}' has been created!")


@bookshelf.command(name='start')
@click.argument('story_name', type=story_type)
@bookshelf.command(name="start")
@click.argument("story_name", type=story_type)
def start_chapter_entry(story_name):
"""Start a new chapter for a story on your bookshelf"""
try:
Expand All @@ -87,11 +98,11 @@ def start_chapter_entry(story_name):
bookshelf_storage.save_story(story)
bookshelf_console.render_story_panel(story)
except KeyboardInterrupt:
bookshelf_console.print(f'📖 A new chapter in \'{story_name}\' has been started!')
bookshelf_console.print(f"📖 A new chapter in '{story_name}' has been started!")


@bookshelf.command(name='stop')
@click.argument('story_name', type=story_type)
@bookshelf.command(name="stop")
@click.argument("story_name", type=story_type)
def stop_chapter_entry(story_name):
"""Stop the current chapter of a story on your bookshelf"""

Expand All @@ -108,8 +119,8 @@ def stop_chapter_entry(story_name):
pass


@bookshelf.command(name='finish')
@click.argument('story_name', type=story_type)
@bookshelf.command(name="finish")
@click.argument("story_name", type=story_type)
def finish_story_entry(story_name):
"""Finish writing a story on your bookshelf"""
try:
Expand All @@ -125,32 +136,40 @@ def finish_story_entry(story_name):
pass


@bookshelf.command(name='ls')
@click.option('--with-tags', type=str, help='Comma-separated list of tags that you wish to filter in.')
@bookshelf.command(name="ls")
@click.option(
"--with-tags",
type=str,
help="Comma-separated list of tags that you wish to filter in.",
)
def list_stories_entry(with_tags: str):
"""List all the current stories on your bookshelf"""
try:
tags_to_filter_by = [tag.strip() for tag in with_tags.split(',')] if with_tags is not None else []
tags_to_filter_by = (
[tag.strip() for tag in with_tags.split(",")]
if with_tags is not None
else []
)
bookshelf_console.render_all_stories_table(tags_to_filter_by=tags_to_filter_by)
except KeyboardInterrupt:
pass


@bookshelf.command(name='rm')
@click.argument('story_name', type=story_type)
@bookshelf.command(name="rm")
@click.argument("story_name", type=story_type)
def remove_story_entry(story_name):
"""Remove a story from your bookshelf"""
try:
story = bookshelf_storage.load_story(story_name)
exit_live_message = 'Press [bold]CTRL+C[/bold] to delete'
exit_live_message = "Press [bold]CTRL+C[/bold] to delete"
bookshelf_console.render_story_panel(story, exit_live_message=exit_live_message)
except KeyboardInterrupt:
bookshelf_storage.delete_story(story_name)
bookshelf_console.print(f'🗑️ Story \'{story_name}\' has been deleted!')
bookshelf_console.print(f"🗑️ Story '{story_name}' has been deleted!")


@bookshelf.command(name='info')
@click.argument('story_name', type=story_type)
@bookshelf.command(name="info")
@click.argument("story_name", type=story_type)
def story_info_entry(story_name: str):
"""Displays the information for a given story on your bookshelf"""
try:
Expand All @@ -160,8 +179,8 @@ def story_info_entry(story_name: str):
pass


@bookshelf.command(name='cancel')
@click.argument('story_name', type=story_type)
@bookshelf.command(name="cancel")
@click.argument("story_name", type=story_type)
def cancel_chapter(story_name):
"""Cancel the current chapter of a story on your bookshelf"""
try:
Expand All @@ -178,26 +197,28 @@ def cancel_chapter(story_name):
bookshelf_storage.save_story(story)
bookshelf_console.render_story_panel(story)
except KeyboardInterrupt:
bookshelf_console.print(f'❌ Current chapter in \'{story_name}\' has been cancelled!')
bookshelf_console.print(
f"❌ Current chapter in '{story_name}' has been cancelled!"
)


@bookshelf.command(name='tag')
@click.argument('story_name', type=story_type)
@click.argument('tag', type=str)
@bookshelf.command(name="tag")
@click.argument("story_name", type=story_type)
@click.argument("tag", type=str)
def tag_story(story_name: str, tag: str):
"""Add a tag to a story on your bookshelf"""
story = bookshelf_storage.load_story(story_name)
story.add_tag(tag)
bookshelf_console.print(f'🏷️ The tag \'{tag}\' has been added to \'{story_name}\'!')
bookshelf_console.print(f"🏷️ The tag '{tag}' has been added to '{story_name}'!")
bookshelf_storage.save_story(story)


@bookshelf.command(name='untag')
@click.argument('story_name', type=story_type)
@click.argument('tag', type=str)
@bookshelf.command(name="untag")
@click.argument("story_name", type=story_type)
@click.argument("tag", type=str)
def untag_story(story_name: str, tag: str):
"""Remove a tag from a story on your bookshelf"""
story = bookshelf_storage.load_story(story_name)
story.remove_tag(tag)
bookshelf_console.print(f'🏷️ The tag \'{tag}\' has been removed from \'{story_name}\'!')
bookshelf_console.print(f"🏷️ The tag '{tag}' has been removed from '{story_name}'!")
bookshelf_storage.save_story(story)
42 changes: 28 additions & 14 deletions bookshelf/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,36 @@ def __init__(self, bookshelf_storage: BookshelfStorage):
super().__init__()
self.bookshelf_storage = bookshelf_storage

def render_all_stories_table(self, tags_to_filter_by: Optional[list[str]] = None) -> None:
def render_all_stories_table(
self, tags_to_filter_by: Optional[list[str]] = None
) -> None:
table = Table(title="📖 Stories 📖", box=box.DOUBLE_EDGE)
table.add_column('✏️ Name', style="cyan", justify="center")
table.add_column('🗓️ Start Date', style="white", justify="center")
table.add_column('🏷️ Tags', style="yellow", justify="center")
table.add_column("✏️ Name", style="cyan", justify="center")
table.add_column("🗓️ Start Date", style="white", justify="center")
table.add_column("🏷️ Tags", style="yellow", justify="center")
for story in self.bookshelf_storage.get_all_stories():
if len(tags_to_filter_by) == 0 or any(tag in story.tags for tag in tags_to_filter_by):
table.add_row(f'{story.name}',
f'{story.start_date.day}/{story.start_date.month}/{story.start_date.year}',
f'{story.tags}')
if len(tags_to_filter_by) == 0 or any(
tag in story.tags for tag in tags_to_filter_by
):
table.add_row(
f"{story.name}",
f"{story.start_date.day}/{story.start_date.month}/{story.start_date.year}",
f"{story.tags}",
)

self.print(table)

def render_story_panel(self, story, exit_live_message: Optional[str] = None, should_clear: bool = True) -> None:
def render_story_panel(
self, story, exit_live_message: Optional[str] = None, should_clear: bool = True
) -> None:
if exit_live_message is None:
exit_live_message = 'Press [bold]CTRL+C[/bold] to exit'
exit_live_message = "Press [bold]CTRL+C[/bold] to exit"

with Live(self._get_story_panel(story, exit_live_message),
transient=should_clear,
refresh_per_second=1) as live_panel:
with Live(
self._get_story_panel(story, exit_live_message),
transient=should_clear,
refresh_per_second=1,
) as live_panel:
while True:
live_panel.update(self._get_story_panel(story, exit_live_message))
time.sleep(0.4)
Expand All @@ -46,5 +56,9 @@ def render_story_panel(self, story, exit_live_message: Optional[str] = None, sho

@staticmethod
def _get_story_panel(story: Story, exit_live_message: str):
panel = Panel(story.to_renderable(exit_live_message=exit_live_message), title='📖 Story 📖', expand=True)
panel = Panel(
story.to_renderable(exit_live_message=exit_live_message),
title="📖 Story 📖",
expand=True,
)
return Padding(panel, 1)
32 changes: 17 additions & 15 deletions bookshelf/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,57 @@
class BookshelfBaseException(Exception):

def __init__(self, message: str):
super().__init__(message)


class BookshelfStoryException(BookshelfBaseException):

def __init__(self, story_name: str, message_template: str, hint_template: str):
self.terminal_message = self._construct_exception_message(story_name, message_template, hint_template)
self.terminal_message = self._construct_exception_message(
story_name, message_template, hint_template
)
super().__init__(message_template.format(story_name))

@staticmethod
def _construct_exception_message(story_name: str, message_template: str, hint_template: str) -> str:
return f'❌{message_template}\n💡{hint_template}'.format(story_name, story_name)
def _construct_exception_message(
story_name: str, message_template: str, hint_template: str
) -> str:
return f"❌{message_template}\n💡{hint_template}".format(story_name, story_name)


class StoryNotFoundException(BookshelfStoryException):
MESSAGE_TEMPLATE = 'Could not find story with name [bold]{}[/bold].'
HINT_TEMPLATE = 'You can create one with [bold]bookshelf create {}[/bold].'
MESSAGE_TEMPLATE = "Could not find story with name [bold]{}[/bold]."
HINT_TEMPLATE = "You can create one with [bold]bookshelf create {}[/bold]."

def __init__(self, story_name):
super().__init__(story_name, self.MESSAGE_TEMPLATE, self.HINT_TEMPLATE)


class StoryAlreadyExistsException(BookshelfStoryException):
MESSAGE_TEMPLATE = 'The story [bold]{}[/bold] already exists.'
HINT_TEMPLATE = 'Use [bold]bookshelf create {} --force [/bold] to override.'
MESSAGE_TEMPLATE = "The story [bold]{}[/bold] already exists."
HINT_TEMPLATE = "Use [bold]bookshelf create {} --force [/bold] to override."

def __init__(self, story_name):
super().__init__(story_name, self.MESSAGE_TEMPLATE, self.HINT_TEMPLATE)


class StoryAlreadyFinishedException(BookshelfStoryException):
MESSAGE_TEMPLATE = 'The story [bold]{}[/bold] is already finished.'
HINT_TEMPLATE = 'Use [bold]bookshelf create {} [/bold] a new story.'
MESSAGE_TEMPLATE = "The story [bold]{}[/bold] is already finished."
HINT_TEMPLATE = "Use [bold]bookshelf create {} [/bold] a new story."

def __init__(self, story_name):
super().__init__(story_name, self.MESSAGE_TEMPLATE, self.HINT_TEMPLATE)


class ChapterInProgressException(BookshelfStoryException):
MESSAGE_TEMPLATE = 'The current chapter in [bold]{}[/bold] is in progress.'
HINT_TEMPLATE = 'Use [bold]bookshelf stop {}[/bold] before starting a new one.'
MESSAGE_TEMPLATE = "The current chapter in [bold]{}[/bold] is in progress."
HINT_TEMPLATE = "Use [bold]bookshelf stop {}[/bold] before starting a new one."

def __init__(self, story_name):
super().__init__(story_name, self.MESSAGE_TEMPLATE, self.HINT_TEMPLATE)


class ChapterNotInProgressException(BookshelfStoryException):
MESSAGE_TEMPLATE = 'The current chapter in {} is not in progress.'
HINT_TEMPLATE = 'Use [bold]bookshelf start {}[/bold] to start one.'
MESSAGE_TEMPLATE = "The current chapter in {} is not in progress."
HINT_TEMPLATE = "Use [bold]bookshelf start {}[/bold] to start one."

def __init__(self, story_name):
super().__init__(story_name, self.MESSAGE_TEMPLATE, self.HINT_TEMPLATE)
Loading
Loading