feat(BREAKING): specify project path
This PR allows users to specify the path to the database file Pelican now expects the user to specify the path to the project's database file which allows users to open different projects. This is a breaking change because Pelican no longer opens the default path automatically. If no path is set then Pelican stops with an error message.
This commit is contained in:
parent
cf586a2b1d
commit
fc5fa7b0ca
5 changed files with 94 additions and 45 deletions
|
@ -1,21 +1,77 @@
|
||||||
= Pelican
|
= Pelican
|
||||||
|
:toc:
|
||||||
|
:toclevels: 1
|
||||||
|
:toc-title: Contents
|
||||||
|
|
||||||
== Summary
|
== Overview
|
||||||
|
|
||||||
**This project is WIP and is not meant for production use**
|
**This project is WIP and is not meant for production use.**
|
||||||
|
|
||||||
Pelican is a simple Kanban board for your terminal.
|
Pelican is a simple Kanban board for your terminal.
|
||||||
|
|
||||||
== Storage
|
|
||||||
|
|
||||||
Data is stored in a https://github.com/etcd-io/bbolt[BoltDB] database.
|
Data is stored in a https://github.com/etcd-io/bbolt[BoltDB] database.
|
||||||
For Linux the default the database file is located at $XDG\_DATA\_HOME/canal/canal.db
|
Pelican does not make any assumptions to the path of the database file;
|
||||||
If $XDG\_DATA\_HOME is not set then the default location is $HOME/.local/share/canal/canal.db by default.
|
instead the user is expected to specify the path when running the application.
|
||||||
For all other operating systems the default location is $HOME/.canal/canal.db.
|
|
||||||
|
== Installation
|
||||||
|
|
||||||
|
=== Requirements
|
||||||
|
|
||||||
|
==== Go
|
||||||
|
|
||||||
|
A minimum version of Go 1.21.0 is required for installing spruce.
|
||||||
|
Please go https://go.dev/dl/[here] to download the latest version.
|
||||||
|
|
||||||
|
==== Mage (Optional)
|
||||||
|
|
||||||
|
The project includes a https://codeflow.dananglin.me.uk/apollo/pelican/src/branch/main/magefiles/mage.go[magefile]
|
||||||
|
for automating the build and installation of the binary.
|
||||||
|
You can visit the https://magefile.org[website] for instructions on how to install Mage.
|
||||||
|
|
||||||
|
=== Install with Mage
|
||||||
|
|
||||||
|
TBC
|
||||||
|
|
||||||
|
=== Install with Go
|
||||||
|
|
||||||
|
If your `GOBIN` directory is included in your `PATH` then you can install Pelican with Go.
|
||||||
|
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
git clone https://codeflow.dananglin.me.uk/apollo/pelican.git
|
||||||
|
cd pelican
|
||||||
|
go install ./cmd/pelican
|
||||||
|
----
|
||||||
|
|
||||||
|
== Running Pelican
|
||||||
|
|
||||||
|
To create a new Kanban project with Pelican, simply run the following command:
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
pelican ./project.pelican
|
||||||
|
----
|
||||||
|
|
||||||
|
This will create a new BoltDB database file called `project.pelican` in your current directory
|
||||||
|
and initialises the database with an empty project.
|
||||||
|
|
||||||
== Keybindings
|
== Keybindings
|
||||||
|
|
||||||
TBC
|
[%header,cols=2*]
|
||||||
|
|===
|
||||||
|
|Key
|
||||||
|
|Action
|
||||||
|
|
||||||
|
|CTRL + q
|
||||||
|
|Quit the application
|
||||||
|
|
||||||
|
|a
|
||||||
|
|Add card
|
||||||
|
|
||||||
|
|CTRL + d
|
||||||
|
|Delete card
|
||||||
|
|
||||||
|
|m
|
||||||
|
|Move card between statuses
|
||||||
|
|===
|
||||||
|
|
||||||
== Inspiration
|
== Inspiration
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,20 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/pelican/internal/ui"
|
"codeflow.dananglin.me.uk/apollo/pelican/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
pelican := ui.NewUI()
|
if len(os.Args) != 2 {
|
||||||
|
log.Fatalf("ERROR: Unexpected number of command-line arguments; want 1; got %d", len(os.Args)-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pelican, err := ui.NewUI(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("ERROR: Unable to initialise Pelican; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := pelican.Run(); err != nil {
|
if err := pelican.Run(); err != nil {
|
||||||
log.Fatalf("Error: an error occurred while running pelican, %s", err)
|
log.Fatalf("Error: an error occurred while running pelican, %s", err)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package board
|
package board
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatusListEmptyError struct{}
|
type StatusListEmptyError struct{}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/pelican/internal/board"
|
"codeflow.dananglin.me.uk/apollo/pelican/internal/board"
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,7 +36,12 @@ type UI struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUI returns a new UI value.
|
// NewUI returns a new UI value.
|
||||||
func NewUI() UI {
|
func NewUI(path string) (UI, error) {
|
||||||
|
b, err := board.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return UI{}, fmt.Errorf("unable to open the project's board; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
ui := UI{
|
ui := UI{
|
||||||
Application: tview.NewApplication(),
|
Application: tview.NewApplication(),
|
||||||
pages: tview.NewPages(),
|
pages: tview.NewPages(),
|
||||||
|
@ -47,13 +51,15 @@ func NewUI() UI {
|
||||||
focusedColumn: 0,
|
focusedColumn: 0,
|
||||||
columns: nil,
|
columns: nil,
|
||||||
move: nil,
|
move: nil,
|
||||||
board: board.Board{},
|
board: b,
|
||||||
deleteCardModal: tview.NewModal(),
|
deleteCardModal: tview.NewModal(),
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.init()
|
if err := ui.init(); err != nil {
|
||||||
|
return UI{}, fmt.Errorf("received an error after running the initialisation; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return ui
|
return ui, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeBoard closes the board.
|
// closeBoard closes the board.
|
||||||
|
@ -78,7 +84,7 @@ func (u *UI) deleteCard() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// init initialises the UI.
|
// init initialises the UI.
|
||||||
func (u *UI) init() {
|
func (u *UI) init() error {
|
||||||
u.pages.AddPage(mainPageName, u.flex, true, true)
|
u.pages.AddPage(mainPageName, u.flex, true, true)
|
||||||
|
|
||||||
u.initQuitModal()
|
u.initQuitModal()
|
||||||
|
@ -90,18 +96,13 @@ func (u *UI) init() {
|
||||||
u.initDeleteCardModal()
|
u.initDeleteCardModal()
|
||||||
u.pages.AddPage(deleteCardPageName, u.deleteCardModal, false, false)
|
u.pages.AddPage(deleteCardPageName, u.deleteCardModal, false, false)
|
||||||
|
|
||||||
u.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
||||||
switch event.Rune() {
|
|
||||||
case 'o':
|
|
||||||
if u.flex.HasFocus() && len(u.columns) == 0 {
|
|
||||||
_ = u.openBoard("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return event
|
|
||||||
})
|
|
||||||
|
|
||||||
u.SetRoot(u.pages, true)
|
u.SetRoot(u.pages, true)
|
||||||
|
|
||||||
|
if err := u.refresh(); err != nil {
|
||||||
|
return fmt.Errorf("error refreshing the board, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// initAddInputModal initialises the add input modal.
|
// initAddInputModal initialises the add input modal.
|
||||||
|
@ -168,22 +169,6 @@ func (u *UI) newCard(title, content string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// openBoard opens the kanban board.
|
|
||||||
func (u *UI) openBoard(path string) error {
|
|
||||||
b, err := board.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load board, %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
u.board = b
|
|
||||||
|
|
||||||
if err = u.refresh(); err != nil {
|
|
||||||
return fmt.Errorf("error refreshing the board, %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// refresh refreshes the UI.
|
// refresh refreshes the UI.
|
||||||
func (u *UI) refresh() error {
|
func (u *UI) refresh() error {
|
||||||
statusList, err := u.board.StatusList()
|
statusList, err := u.board.StatusList()
|
||||||
|
|
Loading…
Reference in a new issue