Skip to content

Commit

Permalink
Merge pull request #3 from Zaloog/centralize-storage
Browse files Browse the repository at this point in the history
Centralize storage
as proposed in #2
  • Loading branch information
Zaloog committed Nov 25, 2023
2 parents 92eb24a + 681f795 commit 19b3793
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 70 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## Version 0.2.0
- Moved the board specific `pykanban.json` files into a dedicated `kanban_boards` directory
in the `.kanban-python` directory under `<BOARDNAME>/pykanban.json`.
This allows centrally stored tasks and doesnt scatter multiple
`pykanban.json` files over your projects.
- Adjusted functions/tests accordingly to new structure
- limiting Namespace of new boardnames to alpha-numeric + `-_ ` due to folder creation
- added default option (active board selection) for board change
- updated docs/readme

## Version 0.1.2
- Instead of `pykanban.ini` configfile in Home Directory
Creates a `.kanban-python` Folder for the respective configfile
Expand Down
2 changes: 1 addition & 1 deletion src/kanban_python/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys

if sys.version_info[:2] >= (3, 8):
# TODO: Import directly (no need for conditional) when `python_requires = >= 3.8`
# Import directly (no need for conditional) when `python_requires = >= 3.8`
from importlib.metadata import PackageNotFoundError, version # pragma: no cover
else:
from importlib_metadata import PackageNotFoundError, version # pragma: no cover
Expand Down
10 changes: 7 additions & 3 deletions src/kanban_python/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import configparser
from pathlib import Path

TASK_FILE = "pykanban.json"
CONFIG_PATH = Path.home() / ".kanban-python"
KANBAN_BOARDS_PATH = CONFIG_PATH / "kanban_boards"
CONFIG_FILE_PATH = CONFIG_PATH / "pykanban.ini"


Expand Down Expand Up @@ -50,7 +52,7 @@ def kanban_boards_dict(self) -> dict:

@kanban_boards_dict.setter
def kanban_boards_dict(self, board_name: str) -> dict:
self.config["kanban_boards"][board_name] = str(Path.cwd())
self.config["kanban_boards"][board_name] = get_json_path(board_name)
self.save()

@property
Expand Down Expand Up @@ -116,6 +118,8 @@ def create_init_config(path=CONFIG_PATH):

if not path.exists():
path.mkdir()
(path / "kanban_boards").mkdir()

with open(path / "pykanban.ini", "w") as configfile:
config.write(configfile)

Expand Down Expand Up @@ -146,5 +150,5 @@ def check_config_exists(path=CONFIG_FILE_PATH) -> bool:
return path.exists()


def check_current_path_exists_for_board(cfg=cfg, path=str(Path.cwd())) -> bool:
return path in cfg.kanban_boards_dict.values()
def get_json_path(boardname: str):
return str(KANBAN_BOARDS_PATH / boardname / TASK_FILE)
58 changes: 32 additions & 26 deletions src/kanban_python/controls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from json import dump, load

from .config import (
KANBAN_BOARDS_PATH,
TASK_FILE,
cfg,
check_current_path_exists_for_board,
check_if_board_name_exists_in_config,
check_if_current_active_board_in_board_list,
delete_board_from_config,
delete_current_folder_board_from_config,
get_json_path,
)
from .interface import (
create_config_table,
Expand All @@ -20,13 +21,12 @@
input_confirm_change_current_settings,
input_confirm_delete_board,
input_confirm_set_board_active,
input_confirm_to_overwrite_db,
input_create_new_task,
input_update_task,
)
from .utils import (
DUMMY_DB,
check_db_exists,
check_board_name_valid,
check_if_done_col_leq_X,
check_if_there_are_visible_tasks_in_board,
console,
Expand All @@ -36,38 +36,43 @@


def create_new_db() -> None:
OVERWRITTEN_FLAG = False
if check_db_exists():
OVERWRITTEN_FLAG = True
if not input_confirm_to_overwrite_db():
return

while True:
new_name = input_ask_for_new_board_name()
if not check_if_board_name_exists_in_config(new_name):
while True:
new_board_name = input_ask_for_new_board_name()
if check_board_name_valid(new_board_name):
break
console.print(f":warning: '{new_board_name}' is [red]not[/] a valid Name.")

if not check_if_board_name_exists_in_config(new_board_name):
break
console.print(
f":warning: Board '{new_name}' already exists, choose a different Name."
f":warning: Board '{new_board_name}' already exists, choose another Name."
)

if OVERWRITTEN_FLAG or check_current_path_exists_for_board():
delete_current_folder_board_from_config()
cfg.active_board = new_name
cfg.kanban_boards_dict = new_board_name

# Options:
# 1. ~/.kanban-python/<BOARDNAME>.json
# 2. ~/.kanban-python/kanban_boards/<BOARDNAME>.json
# 3. ~/.kanban-python/kanban_boards/<BOARDNAME>/pykanban.json <- THIS
# 4. ~/.kanban-python/kanban_boards/<BOARDNAME>/<BOARDNAME>.json
new_db_path = KANBAN_BOARDS_PATH / new_board_name

cfg.kanban_boards_dict = new_name
if not new_db_path.exists():
new_db_path.mkdir()

with open("pykanban.json", "w", encoding="utf-8") as f:
with open(get_json_path(new_board_name), "w", encoding="utf-8") as f:
dump(DUMMY_DB, f, ensure_ascii=False, indent=4)

console.print("Created new [orange3]pykanban.json[/] file to save tasks")
console.print(f"Created new [orange3]{TASK_FILE}[/] file to save tasks")

if input_confirm_set_board_active(name=new_name):
cfg.active_board = new_name
if input_confirm_set_board_active(name=new_board_name):
cfg.active_board = new_board_name


def save_db(data):
path = cfg.active_board_path
with open(f"{path}/pykanban.json", "w", encoding="utf-8") as f:
with open(path, "w", encoding="utf-8") as f:
dump(data, f, ensure_ascii=False, indent=4)


Expand All @@ -86,17 +91,18 @@ def read_db(path: str = None) -> dict:
data = read_single_board(path)
return data
except FileNotFoundError:
console.print(":warning: No [orange3]pykanban.json[/] file here anymore.")
print(path)
console.print(f":warning: No [orange3]{TASK_FILE}[/] file here anymore.")
console.print("Please change to another board.")
change_kanban_board()
console.print("[red]Seems like the previous pykanban.json file was deleted[/]")
console.print("Create new [orange3]pykanban.json[/] file here.")
console.print(f"[red]Seems like the previous {TASK_FILE} file was deleted[/]")
console.print(f"Create new [orange3]{TASK_FILE}[/] file here.")
create_new_db()
return read_db()


def read_single_board(path):
with open(f"{path}/pykanban.json", "r") as file:
with open(path, "r") as file:
data = load(file)
return data

Expand Down
16 changes: 12 additions & 4 deletions src/kanban_python/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rich.prompt import Confirm, IntPrompt, Prompt
from rich.table import Table

from .config import cfg
from .config import TASK_FILE, cfg
from .utils import (
CAPTION_STRING,
COLOR_DICT,
Expand Down Expand Up @@ -162,7 +162,6 @@ def input_update_task(current_task: dict) -> dict:
duration = current_task.get("Duration", 0)

if status == "Done":
print(cfg)
console.print(
f":sparkle: Congrats, you just completed '{title}'"
+ f" after {duration} minutes :muscle:"
Expand Down Expand Up @@ -208,8 +207,9 @@ def input_ask_to_what_status_to_move(task_title):
return possible_status[int(new_status) - 1]


# TODO not needed anymore i guess
def input_confirm_to_overwrite_db() -> bool:
console.print(":warning: Existing [orange3]pykanban.json[/] found :warning:")
console.print(f":warning: Existing [orange3]{TASK_FILE}[/] found :warning:")
return Confirm.ask(
"Do you want to wipe it clean and start from scratch:question_mark:"
)
Expand All @@ -222,18 +222,26 @@ def input_confirm_set_board_active(name) -> bool:


def input_ask_for_new_board_name() -> str:
return Prompt.ask(prompt="What should the new board be called?")
return Prompt.ask(
prompt="A new folder will be created for your board\n"
+ ":warning: [yellow]Only[/] use alpha-numeric characters or"
+ " [green]'-', '_', ' '[/] for new board names.\n"
+ "What should the new board be called?"
)


def input_ask_for_change_board() -> str:
boards = [b for b in cfg.kanban_boards]
active_board_idx = boards.index(cfg.active_board) + 1
for idx, board in enumerate(boards, start=1):
console.print(f"[{idx}] {board}")

answer = IntPrompt.ask(
prompt="Which board to activate",
choices=[f"{i}" for i, _ in enumerate(boards, start=1)],
show_choices=False,
default=active_board_idx,
show_default=True,
)
return boards[int(answer) - 1]

Expand Down
16 changes: 9 additions & 7 deletions src/kanban_python/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ def calculate_time_delta_str(start_time_str: str, end_time_str: str) -> float:
return round(delta_minutes, 2)


def check_db_exists() -> bool:
return Path("pykanban.json").exists()


def create_status_dict_for_rows(data: dict, vis_cols: list) -> dict:
status_dict = {col: [] for col in vis_cols}

Expand Down Expand Up @@ -66,15 +62,21 @@ def move_first_done_task_to_archive(data: dict):
return first_task_id, updated_task


def delete_json_file(db_path) -> None:
path = Path(Path(db_path) / "pykanban.json")
def delete_json_file(db_path: str) -> None:
path = Path(db_path)
try:
path.unlink()
console.print(f"File under {path} was now removed")
path.parent.rmdir()
console.print(f"File under {path.parent} was now removed")
except FileNotFoundError:
console.print("File already deleted")


def check_board_name_valid(boardname: str):
checker = "".join(x for x in boardname if (x.isalnum() or x in "_- "))
return True if (checker == boardname) else False


QUOTES = [
"\n:wave:Stay Hard:wave:",
"\n:wave:See you later:wave:",
Expand Down
14 changes: 0 additions & 14 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,3 @@ def test_delete_board_from_config(test_config, to_delete_board, board_left):

def test_check_config_exists(test_config_filepath, test_config):
assert config.check_config_exists(path=test_config_filepath) is True


@pytest.mark.parametrize(
"board, folder, exists",
[("testboard1", "test1", True), ("testboard2", "test2", False)],
)
def test_check_current_path_exists_for_board(
tmp_path, test_config, board, folder, exists
):
cfg = test_config
board_path = str(tmp_path / "test1" / "pykanban.json")
cfg.config["kanban_boards"][board] = board_path
check_path = str(tmp_path / folder / "pykanban.json")
assert config.check_current_path_exists_for_board(cfg, check_path) == exists
29 changes: 14 additions & 15 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
from datetime import datetime

import pytest
Expand Down Expand Up @@ -49,18 +48,6 @@ def test_move_first_done_task_to_archive(first_task_id):
assert task["Status"] == "Archived"


def test_check_db_exists(tmp_path):
os.chdir(tmp_path)

result = utils.check_db_exists()

assert isinstance(result, bool)

db_file_path = tmp_path / "pykanban.json"
db_file_path.touch()
assert utils.check_db_exists() is True


@pytest.mark.parametrize(
"vis_col, expected_result",
[
Expand Down Expand Up @@ -90,16 +77,28 @@ def test_check_if_there_are_visible_tasks_in_board(test_task, vis_col, expected_
"file_there, output", [(True, "removed"), (False, "File already deleted")]
)
def test_delete_json_file(tmp_path, capsys, file_there, output):
db_file_path = tmp_path / "pykanban.json"
db_path = tmp_path / "boardname"
db_file_path = db_path / "pykanban.json"
if file_there:
db_path.mkdir()
db_file_path.touch()

utils.delete_json_file(tmp_path)
utils.delete_json_file(db_file_path)

captured = capsys.readouterr()
assert output in captured.out


@pytest.mark.parametrize(
"name, expected_result",
[("Test_123", True), ("Test123/", False), (".test_", False)],
)
def test_check_board_name_valid(name, expected_result):
result = utils.check_board_name_valid(name)

assert result is expected_result


# def test_main(capsys):
# """CLI Tests"""
# # capsys is a pytest fixture that allows asserts against stdout/stderr
Expand Down

0 comments on commit 19b3793

Please sign in to comment.