Compare commits

..

2 Commits

Author SHA1 Message Date
78931145ac Update some bad formatting 2024-01-11 22:32:35 -05:00
8a67085d5b Latest working changes 2024-01-11 22:18:25 -05:00
6 changed files with 182 additions and 74 deletions

View File

@ -1,6 +1,5 @@
# PyKanban # PyKanban
**Note this project has been abandoned. I ended up not liking Textual as a TUI library. I plan on creating another project to accomplish a similar goal using rust.**
A python implementation of a simple kanban board for managing personal projects. A python implementation of a simple kanban board for managing personal projects.
## License ## License

View File

@ -15,13 +15,11 @@ requires-python = ">=3.8"
classifiers = [ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
"Operating System :: OS Independent", "Operating System :: Unix",
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Environment :: Console" "Environment :: Console"
] ]
dependencies = [
"textual"
]
[project.urls] [project.urls]
Homepage = "https://alexselimov.com/git/aselimov/pykanban" Homepage = "https://alexselimov.com/git/aselimov/pykanban"
Issues = "https://github.com/aselimov/PyKanban/issues" Issues = "https://github.com/aselimov/PyKanban/issues"

View File

@ -1,19 +0,0 @@
columns:
- To Do
- In Progress
- Review
- Done
tasks:
- column: To Do
description: "We want to be able to retire some done tickets \nwithout having to\
\ delete them."
score: '5'
summary: Work out some way to handle sprints
- column: To Do
description: 'I want to add footers which describe the key shortcuts,
Additionally this should be disabledable via a command line argument
'
score: '5'
summary: 'Add some footers for the key shortcuts '

View File

@ -1,14 +1,17 @@
""" This module contains classes and functions to contain the kanban board information """ """ This module contains classes and functions to contain the kanban board information """
import sys
import numpy as np import numpy as np
import yaml import yaml
import os
import glob
class Task: class Task:
""" This class represents each task, """This class represents each task,"""
"""
def __init__(self, summary, score, description): def __init__(self, summary, score, description):
""" Initialize the task class """Initialize the task class"""
"""
# Each task has the following properties # Each task has the following properties
self.summary = summary # Summary of the task self.summary = summary # Summary of the task
self.score = score # Score for ticket self.score = score # Score for ticket
@ -18,11 +21,9 @@ class Task:
class Board: class Board:
def __init__(self, file=None): def __init__(self, file=None):
"""Initialize the Board class, this class has three important class variables. """Initialize the Board class, this class has three important class variables.
These are: These are: self.sprint | str - name of the current sprint
self.sprint | str - name of the current sprint
self.columns | list(str) - columns in kanban board self.columns | list(str) - columns in kanban board
self.tasks | list(list()) - tasks in each column self.tasks | list(list()) - tasks in each column
""" """
self.sprint = None self.sprint = None
self.columns = list() self.columns = list()
@ -33,7 +34,6 @@ class Board:
if file: if file:
self.read_yaml(file) self.read_yaml(file)
def read_yaml(self, file): def read_yaml(self, file):
"""Read the yaml file in and set up the data """Read the yaml file in and set up the data
@ -42,15 +42,16 @@ class Board:
""" """
# Read in the data # Read in the data
with open(file, 'r') as f: with open(file, "r") as f:
data = yaml.safe_load(f) data = yaml.safe_load(f)
# Assign the data to board variables # Assign the data to board variables
self.columns = data['columns'] self.columns = data["columns"]
self.tasks = [[] for col in self.columns] self.tasks = [[] for col in self.columns]
for task in data['tasks']: for task in data["tasks"]:
self.tasks[self.columns.index(task['column'])].append( self.tasks[self.columns.index(task["column"])].append(
Task(task['summary'], task['score'], task['description'])) Task(task["summary"], task["score"], task["description"])
)
def write_yaml(self, file): def write_yaml(self, file):
"""Write the yaml file """Write the yaml file
@ -61,17 +62,22 @@ class Board:
# Set up data to write out # Set up data to write out
data = dict() data = dict()
data['columns'] = self.columns data["columns"] = self.columns
data['tasks'] = list() data["tasks"] = list()
for col, task_list in zip(self.columns, self.tasks): for col, task_list in zip(self.columns, self.tasks):
for task in task_list: for task in task_list:
data['tasks'].append({'column':col, 'summary':task.summary, 'score':task.score, data["tasks"].append(
'description':task.description}) {
"column": col,
"summary": task.summary,
"score": task.score,
"description": task.description,
}
)
with open(file, 'w') as f: with open(file, "w") as f:
yaml.dump(data, f) yaml.dump(data, f)
def move_task(self, col_index, task_index, direction): def move_task(self, col_index, task_index, direction):
"""This class method moves tasks between columns by incrementing/decrementing the column """This class method moves tasks between columns by incrementing/decrementing the column
index index
@ -93,8 +99,6 @@ class Board:
else: else:
return False return False
def print_board_items(self): def print_board_items(self):
for i, col in enumerate(self.columns): for i, col in enumerate(self.columns):
print(col) print(col)
@ -123,3 +127,30 @@ class Board:
def del_task(self, icol, itask): def del_task(self, icol, itask):
del self.tasks[icol][itask] del self.tasks[icol][itask]
class BoardList:
"""This class is used to process the full list of boards"""
def __init__(self):
self.boards = self.get_boards()
def get_boards(self):
"""This function returns the boards that have been created"""
configpath = os.path.join(
os.environ.get("APPDATA")
or os.environ.get("XDG_CONFIG_HOME")
or os.path.join(os.environ["HOME"], ".config"),
"pykban",
)
boards = list()
for board in glob.glob(os.path.join(configpath, "(*.yaml)")):
with open(board, "r") as f:
data = yaml.safe_load(f)
try:
boards.append((data["name"], board))
except KeyError:
print("Board yaml file is missing the name attribute")
sys.exit()
return boards

View File

@ -19,6 +19,14 @@ EditColScreen {
layout: vertical; layout: vertical;
background: #000000 25%; background: #000000 25%;
} }
SelectBoardScreen{
align: center middle;
overflow-x: hidden;
layout: vertical;
background: #000000 25%;
}
.column { .column {
width: 1fr; width: 1fr;
height: 100%; height: 100%;

View File

@ -3,12 +3,7 @@ from textual.widgets import Static, Label, ListItem, ListView, TextArea, Input
from textual.containers import Horizontal, Vertical from textual.containers import Horizontal, Vertical
from textual.screen import Screen from textual.screen import Screen
from textual.binding import Binding from textual.binding import Binding
from .board import Board, Task from .board import Board, Task, BoardList
def run_tui():
kb = KanbanForm()
kb.run()
class TaskList(ListView): class TaskList(ListView):
@ -137,7 +132,72 @@ class EditColScreen(Screen):
self.dismiss(query.nodes[0].value) self.dismiss(query.nodes[0].value)
class KanbanForm(App): class SelectBoardScreen(Screen):
"""This is a screen used to select a board"""
BINDINGS = [
Binding("enter", "pick_option", "Save Changes", priority=True),
Binding("q", "exit", "Exit"),
]
CSS = """
$bg: #282828;
Label{
width:50%;
background: #282828;
padding: 0;
}
TaskList{
width:50%;
background: #282828;
padding: 0 0;
border: #ebdbb9;
}
ListView{
width:50%;
background: #282828;
}
ListItem{
border: solid #ebdbb2 100%;
background: $bg;
}
ListView > ListItem.--highlight {
background: $bg;
}
ListView:focus > ListItem.--highlight {
background: #458588;
}
Label:focus{
background: #458588;
}
"""
def __init__(self, logger):
"""
Initialize the screen
"""
super().__init__()
self.board_list = BoardList()
self.logger = logger
def compose(self):
"""
Compose the widgets on the screen, this screen doesn't need dynamic layout changes
"""
yield Label("Select a board:")
yield TaskList(
*[ListItem(Label(board)) for board in self.board_list.get_boards()],
ListItem(Label("Add a new board")),
)
def action_pick_option(self):
"""Pick a board from the ListItem"""
self.focused.highlighted_child
class MainBoardScreen(Screen):
CSS_PATH = "layout.tcss" CSS_PATH = "layout.tcss"
BINDINGS = [ BINDINGS = [
Binding( Binding(
@ -173,7 +233,7 @@ class KanbanForm(App):
show=False, show=False,
), ),
Binding( Binding(
"x", "d",
"delete_task", "delete_task",
"Delete Task", "Delete Task",
show=False, show=False,
@ -181,6 +241,10 @@ class KanbanForm(App):
Binding("q", "exit", "Exit"), Binding("q", "exit", "Exit"),
] ]
def __init__(self):
"""Initialize the Kanban Form App"""
super().__init__()
def compose(self): def compose(self):
""" """
Initialization function for form Initialization function for form
@ -203,7 +267,10 @@ class KanbanForm(App):
else: else:
yield Static(col, classes="header") yield Static(col, classes="header")
yield TaskList( yield TaskList(
*[ListItem(Label(task.summary)) for task in self.board.get_tasks()[i]] *[
ListItem(Label(task.summary))
for task in self.board.get_tasks()[i]
]
) )
def action_fnext(self): def action_fnext(self):
@ -324,3 +391,27 @@ class KanbanForm(App):
self.focused.mount(ListItem(Label(task.summary))) self.focused.mount(ListItem(Label(task.summary)))
self.board.add_task(icol, task) self.board.add_task(icol, task)
self.focused.action_cursor_down() self.focused.action_cursor_down()
class KanbanForm(App):
"""Main Kanban app"""
CSS_PATH = "layout.tcss"
SCREENS = {"main": SelectBoardScreen()}
def on_mount(self):
self.push_screen("main")
# def on_key(self):
# with open('log','a') as f:
# f.write("{}".format(self.children[0].focus_next))
def run_tui():
kb = KanbanForm()
kb.run()
if __name__ == "__main__":
run_tui()