progress
This commit is contained in:
parent
1d90354fd3
commit
c85e192eb8
41
CLAUDE.md
Normal file
41
CLAUDE.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Bevy Tasks is a local-first, cross-platform task management app built in Rust. Tasks are stored as markdown files with YAML frontmatter in user-selected folders. Currently in Phase 1 (Core Library & CLI MVP). The GUI crate is a placeholder for Phase 3+.
|
||||||
|
|
||||||
|
## Build & Test Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build # Build all crates
|
||||||
|
cargo build -p bevy-tasks-cli # Build CLI only
|
||||||
|
cargo test # Run all tests
|
||||||
|
cargo test -p bevy-tasks-core # Run core library tests only
|
||||||
|
cargo run -p bevy-tasks-cli -- <args> # Run CLI with arguments
|
||||||
|
```
|
||||||
|
|
||||||
|
The CLI binary is named `bevy-tasks` (from the `bevy-tasks-cli` crate).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Three-crate workspace (`resolver = "2"`, edition 2021):
|
||||||
|
|
||||||
|
- **bevy-tasks-core** — Pure Rust library. Storage trait with `FileSystemStorage` implementation, `TaskRepository` (main API), data models, config, error types. No CLI/UI dependencies.
|
||||||
|
- **bevy-tasks-cli** — CLI frontend using clap. Commands are in `src/commands/` (init, workspace, list, task, group). Output formatting in `src/output.rs`.
|
||||||
|
- **bevy-tasks-gui** — Placeholder for future egui/eframe GUI.
|
||||||
|
|
||||||
|
### Key patterns
|
||||||
|
|
||||||
|
- **Storage trait** (`storage.rs`): Strategy pattern for task persistence. `FileSystemStorage` reads/writes markdown files with YAML frontmatter and JSON metadata files.
|
||||||
|
- **Repository** (`repository.rs`): `TaskRepository` wraps a `Storage` impl and provides the public API for task/list CRUD, ordering, and grouping. Tests live here.
|
||||||
|
- **Config** (`config.rs`): `AppConfig` manages named workspaces with paths. Stored in platform-specific config dirs via the `directories` crate.
|
||||||
|
|
||||||
|
### On-disk format
|
||||||
|
|
||||||
|
Workspaces are plain folders. Each task list is a subfolder containing `.listdata.json` (metadata/ordering) and one `.md` file per task. The workspace root has `.metadata.json` for list ordering.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
See `PLAN.md` for the 7-phase roadmap. Phase 1 is complete. Detailed API docs in `docs/API.md`, development practices in `docs/DEVELOPMENT.md`.
|
||||||
4564
Cargo.lock
generated
4564
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
82
PLAN.md
82
PLAN.md
|
|
@ -191,14 +191,15 @@ impl TaskRepository {
|
||||||
|
|
||||||
// Task operations
|
// Task operations
|
||||||
pub fn create_task(&mut self, list_id: Uuid, task: Task) -> Result<Task>;
|
pub fn create_task(&mut self, list_id: Uuid, task: Task) -> Result<Task>;
|
||||||
pub fn get_task(&self, id: Uuid) -> Result<Task>;
|
pub fn get_task(&self, list_id: Uuid, task_id: Uuid) -> Result<Task>;
|
||||||
pub fn update_task(&mut self, task: Task) -> Result<()>;
|
pub fn update_task(&mut self, list_id: Uuid, task: Task) -> Result<()>;
|
||||||
pub fn delete_task(&mut self, id: Uuid) -> Result<()>;
|
pub fn delete_task(&mut self, list_id: Uuid, task_id: Uuid) -> Result<()>;
|
||||||
pub fn list_tasks(&self, list_id: Uuid) -> Result<Vec<Task>>;
|
pub fn list_tasks(&self, list_id: Uuid) -> Result<Vec<Task>>;
|
||||||
|
|
||||||
// List operations
|
// List operations
|
||||||
pub fn create_list(&mut self, name: String) -> Result<TaskList>;
|
pub fn create_list(&mut self, name: String) -> Result<TaskList>;
|
||||||
pub fn get_lists(&self) -> Result<Vec<TaskList>>;
|
pub fn get_lists(&self) -> Result<Vec<TaskList>>;
|
||||||
|
pub fn get_list(&self, list_id: Uuid) -> Result<TaskList>;
|
||||||
pub fn delete_list(&mut self, id: Uuid) -> Result<()>;
|
pub fn delete_list(&mut self, id: Uuid) -> Result<()>;
|
||||||
|
|
||||||
// Task ordering (modifies .listdata.json)
|
// Task ordering (modifies .listdata.json)
|
||||||
|
|
@ -242,17 +243,15 @@ tokio = { version = "1.40", features = ["full"] }
|
||||||
[package]
|
[package]
|
||||||
name = "bevy-tasks-core"
|
name = "bevy-tasks-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.9" # YAML frontmatter
|
serde_yaml = "0.9" # YAML frontmatter
|
||||||
pulldown-cmark = "0.12" # Markdown parsing
|
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
directories = "5.0"
|
directories = "5.0"
|
||||||
anyhow = { workspace = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.0"
|
tempfile = "3.0"
|
||||||
|
|
@ -263,7 +262,7 @@ tempfile = "3.0"
|
||||||
[package]
|
[package]
|
||||||
name = "bevy-tasks-cli"
|
name = "bevy-tasks-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "bevy-tasks"
|
name = "bevy-tasks"
|
||||||
|
|
@ -273,36 +272,35 @@ path = "src/main.rs"
|
||||||
bevy-tasks-core = { path = "../bevy-tasks-core" }
|
bevy-tasks-core = { path = "../bevy-tasks-core" }
|
||||||
clap = { version = "4.5", features = ["derive", "env"] }
|
clap = { version = "4.5", features = ["derive", "env"] }
|
||||||
colored = "2.0"
|
colored = "2.0"
|
||||||
indicatif = "0.17"
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
tokio = { workspace = true }
|
fs_extra = "1.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- [ ] Cargo workspace setup
|
- [x] Cargo workspace setup
|
||||||
- [ ] Data models (Task, TaskList, AppConfig, WorkspaceConfig)
|
- [x] Data models (Task, TaskList, AppConfig, WorkspaceConfig)
|
||||||
- [ ] Markdown file I/O with YAML frontmatter parsing
|
- [x] Markdown file I/O with YAML frontmatter parsing
|
||||||
- [ ] Local storage implementation
|
- [x] Local storage implementation
|
||||||
- [ ] Repository pattern and public API
|
- [x] Repository pattern and public API
|
||||||
- [ ] Multiple workspace support
|
- [x] Multiple workspace support
|
||||||
- [ ] CLI: `init` command (create named workspace)
|
- [x] CLI: `init` command (create named workspace)
|
||||||
- [ ] CLI: `workspace add` command (add additional workspaces)
|
- [x] CLI: `workspace add` command (add additional workspaces)
|
||||||
- [ ] CLI: `workspace list` command (view all workspaces)
|
- [x] CLI: `workspace list` command (view all workspaces)
|
||||||
- [ ] CLI: `workspace switch` command (change current workspace)
|
- [x] CLI: `workspace switch` command (change current workspace)
|
||||||
- [ ] CLI: `workspace remove` command (delete workspace)
|
- [x] CLI: `workspace remove` command (delete workspace)
|
||||||
- [ ] CLI: `workspace retarget` command (update workspace path without moving files)
|
- [x] CLI: `workspace retarget` command (update workspace path without moving files)
|
||||||
- [ ] CLI: `workspace migrate` command (move files to new location)
|
- [x] CLI: `workspace migrate` command (move files to new location)
|
||||||
- [ ] CLI: `list create` command (create new task lists)
|
- [x] CLI: `list create` command (create new task lists)
|
||||||
- [ ] CLI: `list` command (view tasks)
|
- [x] CLI: `list` command (view tasks)
|
||||||
- [ ] CLI: `add` command (create tasks)
|
- [x] CLI: `add` command (create tasks)
|
||||||
- [ ] CLI: `complete` command (mark done)
|
- [x] CLI: `complete` command (mark done)
|
||||||
- [ ] CLI: `delete` command (remove tasks)
|
- [x] CLI: `delete` command (remove tasks)
|
||||||
- [ ] CLI: `edit` command (modify tasks - CLI only, creates temp file)
|
- [x] CLI: `edit` command (modify tasks - CLI only, creates temp file)
|
||||||
- [ ] Manual task ordering (always via task_order array)
|
- [x] Manual task ordering (always via task_order array)
|
||||||
- [ ] CLI: `group` command (toggle group-by-due-date for a list)
|
- [x] CLI: `group` command (toggle group-by-due-date for a list)
|
||||||
- [ ] Support for `--workspace` flag on all commands
|
- [x] Support for `--workspace` flag on all commands
|
||||||
- [ ] Comprehensive unit and integration tests (>80% coverage)
|
- [x] Comprehensive unit and integration tests (>80% coverage)
|
||||||
|
|
||||||
### CLI Usage Examples
|
### CLI Usage Examples
|
||||||
|
|
||||||
|
|
@ -347,7 +345,7 @@ $ bevy-tasks add "Team meeting" --workspace shared
|
||||||
✓ Created task "Team meeting" in workspace "shared"
|
✓ Created task "Team meeting" in workspace "shared"
|
||||||
|
|
||||||
# List all tasks (from current workspace)
|
# List all tasks (from current workspace)
|
||||||
$ bevy-tasks list
|
$ bevy-tasks list show
|
||||||
My Tasks (3 tasks)
|
My Tasks (3 tasks)
|
||||||
[ ] Buy groceries
|
[ ] Buy groceries
|
||||||
[ ] Call dentist
|
[ ] Call dentist
|
||||||
|
|
@ -358,13 +356,13 @@ Work (2 tasks)
|
||||||
[ ] Team meeting prep
|
[ ] Team meeting prep
|
||||||
|
|
||||||
# List tasks from specific workspace
|
# List tasks from specific workspace
|
||||||
$ bevy-tasks list --workspace shared
|
$ bevy-tasks list show --workspace shared
|
||||||
Shared Tasks (2 tasks)
|
Shared Tasks (2 tasks)
|
||||||
[ ] Team meeting
|
[ ] Team meeting
|
||||||
[ ] Quarterly planning
|
[ ] Quarterly planning
|
||||||
|
|
||||||
# List tasks in specific list
|
# List tasks in specific list
|
||||||
$ bevy-tasks list --list "Work"
|
$ bevy-tasks list show --list "Work"
|
||||||
Work (2 tasks)
|
Work (2 tasks)
|
||||||
[ ] Review PR #123 (due: 2025-11-15)
|
[ ] Review PR #123 (due: 2025-11-15)
|
||||||
[ ] Team meeting prep
|
[ ] Team meeting prep
|
||||||
|
|
@ -414,11 +412,11 @@ $ bevy-tasks group disable --list "Personal"
|
||||||
|
|
||||||
### Deliverables
|
### Deliverables
|
||||||
|
|
||||||
- [ ] `bevy-tasks-core` library with stable API
|
- [x] `bevy-tasks-core` library with stable API
|
||||||
- [ ] Functional CLI that can manage tasks
|
- [x] Functional CLI that can manage tasks
|
||||||
- [ ] Data persists as Obsidian-compatible .md files
|
- [x] Data persists as Obsidian-compatible .md files
|
||||||
- [ ] Well-tested backend (>80% coverage)
|
- [x] Well-tested backend (>80% coverage)
|
||||||
- [ ] Documentation for core library API
|
- [x] Documentation for core library API
|
||||||
|
|
||||||
### Development Setup
|
### Development Setup
|
||||||
|
|
||||||
|
|
@ -980,6 +978,6 @@ This project is free and open-source software licensed under GPL v3.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: 2025-10-27
|
**Last Updated**: 2026-03-17
|
||||||
**Document Version**: 3.0
|
**Document Version**: 3.1
|
||||||
**Status**: Ready to Implement - Milestone-Driven Plan
|
**Status**: Ready to Implement - Milestone-Driven Plan
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ path = "src/main.rs"
|
||||||
bevy-tasks-core = { path = "../bevy-tasks-core" }
|
bevy-tasks-core = { path = "../bevy-tasks-core" }
|
||||||
clap = { version = "4.5", features = ["derive", "env"] }
|
clap = { version = "4.5", features = ["derive", "env"] }
|
||||||
colored = "2.0"
|
colored = "2.0"
|
||||||
indicatif = "0.17"
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
tokio = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
uuid = { workspace = true }
|
||||||
fs_extra = "1.3"
|
fs_extra = "1.3"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::output;
|
||||||
use crate::commands::get_repository;
|
use crate::commands::get_repository;
|
||||||
|
|
||||||
pub fn enable(list_name: String, workspace: Option<String>) -> Result<()> {
|
pub fn enable(list_name: String, workspace: Option<String>) -> Result<()> {
|
||||||
let (mut repo, workspace_name) = get_repository(workspace)?;
|
let (mut repo, _workspace_name) = get_repository(workspace)?;
|
||||||
|
|
||||||
let lists = repo.get_lists()
|
let lists = repo.get_lists()
|
||||||
.context("Failed to get lists")?;
|
.context("Failed to get lists")?;
|
||||||
|
|
@ -21,7 +21,7 @@ pub fn enable(list_name: String, workspace: Option<String>) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable(list_name: String, workspace: Option<String>) -> Result<()> {
|
pub fn disable(list_name: String, workspace: Option<String>) -> Result<()> {
|
||||||
let (mut repo, workspace_name) = get_repository(workspace)?;
|
let (mut repo, _workspace_name) = get_repository(workspace)?;
|
||||||
|
|
||||||
let lists = repo.get_lists()
|
let lists = repo.get_lists()
|
||||||
.context("Failed to get lists")?;
|
.context("Failed to get lists")?;
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,12 @@ pub fn execute(path: String, name: String) -> Result<()> {
|
||||||
let mut repo = TaskRepository::init(path_buf.clone())
|
let mut repo = TaskRepository::init(path_buf.clone())
|
||||||
.context("Failed to initialize tasks folder")?;
|
.context("Failed to initialize tasks folder")?;
|
||||||
|
|
||||||
// Create default list
|
// Create default list if it doesn't exist
|
||||||
|
let lists = repo.get_lists().context("Failed to get lists")?;
|
||||||
|
if !lists.iter().any(|l| l.title == "My Tasks") {
|
||||||
repo.create_list("My Tasks".to_string())
|
repo.create_list("My Tasks".to_string())
|
||||||
.context("Failed to create default list")?;
|
.context("Failed to create default list")?;
|
||||||
|
}
|
||||||
|
|
||||||
// Load or create config
|
// Load or create config
|
||||||
let config_path = AppConfig::get_config_path();
|
let config_path = AppConfig::get_config_path();
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::output;
|
||||||
use crate::commands::get_repository;
|
use crate::commands::get_repository;
|
||||||
|
|
||||||
pub fn create(name: String, workspace: Option<String>) -> Result<()> {
|
pub fn create(name: String, workspace: Option<String>) -> Result<()> {
|
||||||
let (mut repo, workspace_name) = get_repository(workspace)?;
|
let (mut repo, _workspace_name) = get_repository(workspace)?;
|
||||||
|
|
||||||
repo.create_list(name.clone())
|
repo.create_list(name.clone())
|
||||||
.context("Failed to create list")?;
|
.context("Failed to create list")?;
|
||||||
|
|
@ -15,7 +15,7 @@ pub fn create(name: String, workspace: Option<String>) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(list_name: Option<String>, workspace: Option<String>) -> Result<()> {
|
pub fn show(list_name: Option<String>, workspace: Option<String>) -> Result<()> {
|
||||||
let (repo, workspace_name) = get_repository(workspace)?;
|
let (repo, _workspace_name) = get_repository(workspace)?;
|
||||||
|
|
||||||
let lists = repo.get_lists()
|
let lists = repo.get_lists()
|
||||||
.context("Failed to get lists")?;
|
.context("Failed to get lists")?;
|
||||||
|
|
@ -49,7 +49,8 @@ pub fn show(list_name: Option<String>, workspace: Option<String>) -> Result<()>
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
println!(" {} {}{}", checkbox, task.title, due_str);
|
let id_str = task.id.to_string();
|
||||||
|
println!(" {} {}{} {}", checkbox, task.title, due_str, id_str.dimmed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -73,7 +74,8 @@ pub fn show(list_name: Option<String>, workspace: Option<String>) -> Result<()>
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
println!(" {} {}{}", checkbox, task.title, due_str);
|
let id_str = task.id.to_string();
|
||||||
|
println!(" {} {}{} {}", checkbox, task.title, due_str, id_str.dimmed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
|
|
@ -84,7 +86,7 @@ pub fn show(list_name: Option<String>, workspace: Option<String>) -> Result<()>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(name: String, workspace: Option<String>) -> Result<()> {
|
pub fn delete(name: String, workspace: Option<String>) -> Result<()> {
|
||||||
let (mut repo, workspace_name) = get_repository(workspace)?;
|
let (mut repo, _workspace_name) = get_repository(workspace)?;
|
||||||
|
|
||||||
let lists = repo.get_lists()
|
let lists = repo.get_lists()
|
||||||
.context("Failed to get lists")?;
|
.context("Failed to get lists")?;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use bevy_tasks_core::{Task, TaskStatus};
|
use bevy_tasks_core::Task;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use std::io::Write;
|
|
||||||
use crate::output;
|
use crate::output;
|
||||||
use crate::commands::get_repository;
|
use crate::commands::get_repository;
|
||||||
|
|
||||||
pub fn add(title: String, list_name: Option<String>, due_str: Option<String>, workspace: Option<String>) -> Result<()> {
|
pub fn add(title: String, list_name: Option<String>, due_str: Option<String>, workspace: Option<String>) -> Result<()> {
|
||||||
let (mut repo, workspace_name) = get_repository(workspace)?;
|
let (mut repo, _workspace_name) = get_repository(workspace)?;
|
||||||
|
|
||||||
// Get lists
|
// Get lists
|
||||||
let lists = repo.get_lists()
|
let lists = repo.get_lists()
|
||||||
|
|
@ -52,7 +51,7 @@ pub fn add(title: String, list_name: Option<String>, due_str: Option<String>, wo
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete(task_id_str: String, workspace: Option<String>) -> Result<()> {
|
pub fn complete(task_id_str: String, workspace: Option<String>) -> Result<()> {
|
||||||
let (mut repo, workspace_name) = get_repository(workspace)?;
|
let (mut repo, _workspace_name) = get_repository(workspace)?;
|
||||||
|
|
||||||
let task_id = Uuid::parse_str(&task_id_str)
|
let task_id = Uuid::parse_str(&task_id_str)
|
||||||
.context("Invalid task ID")?;
|
.context("Invalid task ID")?;
|
||||||
|
|
@ -81,7 +80,7 @@ pub fn complete(task_id_str: String, workspace: Option<String>) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(task_id_str: String, workspace: Option<String>) -> Result<()> {
|
pub fn delete(task_id_str: String, workspace: Option<String>) -> Result<()> {
|
||||||
let (mut repo, workspace_name) = get_repository(workspace)?;
|
let (mut repo, _workspace_name) = get_repository(workspace)?;
|
||||||
|
|
||||||
let task_id = Uuid::parse_str(&task_id_str)
|
let task_id = Uuid::parse_str(&task_id_str)
|
||||||
.context("Invalid task ID")?;
|
.context("Invalid task ID")?;
|
||||||
|
|
@ -111,7 +110,7 @@ pub fn delete(task_id_str: String, workspace: Option<String>) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn edit(task_id_str: String, workspace: Option<String>) -> Result<()> {
|
pub fn edit(task_id_str: String, workspace: Option<String>) -> Result<()> {
|
||||||
let (mut repo, workspace_name) = get_repository(workspace)?;
|
let (mut repo, _workspace_name) = get_repository(workspace)?;
|
||||||
|
|
||||||
let task_id = Uuid::parse_str(&task_id_str)
|
let task_id = Uuid::parse_str(&task_id_str)
|
||||||
.context("Invalid task ID")?;
|
.context("Invalid task ID")?;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use bevy_tasks_core::{AppConfig, TaskRepository, WorkspaceConfig};
|
use bevy_tasks_core::{TaskRepository, WorkspaceConfig};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use crate::output;
|
use crate::output;
|
||||||
|
|
@ -17,9 +17,12 @@ pub fn add(name: String, path: String) -> Result<()> {
|
||||||
let mut repo = TaskRepository::init(path_buf.clone())
|
let mut repo = TaskRepository::init(path_buf.clone())
|
||||||
.context("Failed to initialize tasks folder")?;
|
.context("Failed to initialize tasks folder")?;
|
||||||
|
|
||||||
// Create default list
|
// Create default list if it doesn't exist
|
||||||
|
let lists = repo.get_lists().context("Failed to get lists")?;
|
||||||
|
if !lists.iter().any(|l| l.title == "My Tasks") {
|
||||||
repo.create_list("My Tasks".to_string())
|
repo.create_list("My Tasks".to_string())
|
||||||
.context("Failed to create default list")?;
|
.context("Failed to create default list")?;
|
||||||
|
}
|
||||||
|
|
||||||
// Load config
|
// Load config
|
||||||
let mut config = load_config()?;
|
let mut config = load_config()?;
|
||||||
|
|
@ -51,7 +54,10 @@ pub fn list() -> Result<()> {
|
||||||
|
|
||||||
let current = config.current_workspace.as_deref();
|
let current = config.current_workspace.as_deref();
|
||||||
|
|
||||||
for (name, workspace_config) in &config.workspaces {
|
let mut workspaces: Vec<_> = config.workspaces.iter().collect();
|
||||||
|
workspaces.sort_by(|a, b| a.0.cmp(b.0));
|
||||||
|
|
||||||
|
for (name, workspace_config) in workspaces {
|
||||||
let marker = if Some(name.as_str()) == current {
|
let marker = if Some(name.as_str()) == current {
|
||||||
" (current)".green()
|
" (current)".green()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -175,8 +175,7 @@ enum GroupCommands {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
fn main() -> Result<()> {
|
||||||
async fn main() -> Result<()> {
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ serde_yaml = "0.9"
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
directories = "5.0"
|
directories = "5.0"
|
||||||
anyhow = { workspace = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.0"
|
tempfile = "3.0"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,6 @@ impl FileSystemStorage {
|
||||||
|
|
||||||
fn list_dir_path(&self, list_id: Uuid) -> Result<PathBuf> {
|
fn list_dir_path(&self, list_id: Uuid) -> Result<PathBuf> {
|
||||||
// Find the directory with this list ID
|
// Find the directory with this list ID
|
||||||
let metadata = self.read_root_metadata()?;
|
|
||||||
let entries = fs::read_dir(&self.root_path)?;
|
let entries = fs::read_dir(&self.root_path)?;
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
|
|
@ -251,6 +250,23 @@ impl Storage for FileSystemStorage {
|
||||||
let list_dir = self.list_dir_path(list_id)?;
|
let list_dir = self.list_dir_path(list_id)?;
|
||||||
let task_path = self.task_file_path(&list_dir, task);
|
let task_path = self.task_file_path(&list_dir, task);
|
||||||
|
|
||||||
|
// Remove old file if task was renamed (different filename, same ID)
|
||||||
|
for entry in fs::read_dir(&list_dir)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path == task_path { continue; }
|
||||||
|
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("md") {
|
||||||
|
if let Ok(content) = fs::read_to_string(&path) {
|
||||||
|
if let Ok((fm, _)) = self.parse_markdown_with_frontmatter(&content) {
|
||||||
|
if fm.id == task.id {
|
||||||
|
fs::remove_file(&path)?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let content = self.write_markdown_with_frontmatter(task)?;
|
let content = self.write_markdown_with_frontmatter(task)?;
|
||||||
fs::write(&task_path, content)?;
|
fs::write(&task_path, content)?;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue