Skip to content

Commit

Permalink
🎨 Use Ruff Formatter (#42)
Browse files Browse the repository at this point in the history
* 🎨 Applying Ruff linting

* 👷 Updating CI/CD to use Ruff

* 🎨 Add Ruff formatter badge
  • Loading branch information
jossmoff authored Jan 13, 2024
1 parent 5323210 commit 60ec2d2
Show file tree
Hide file tree
Showing 17 changed files with 375 additions and 267 deletions.
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

0 comments on commit 60ec2d2

Please sign in to comment.