diff --git a/src/cli.rs b/src/cli.rs index f16d4a03c3f0093cc1506973708f85458842f34d..cf5649e889a05f80403d3cca752420d797f4a535 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -17,21 +17,12 @@ pub struct Cli { #[derive(Subcommand)] pub enum Command { - /// Initialize a central project and bind the current directory to it - Init { - /// Project name - name: String, - }, - - /// Bind the current directory to an existing project - Use { - /// Project name - name: String, + /// Manage projects + Project { + #[command(subcommand)] + action: ProjectAction, }, - /// List all known projects in central storage - Projects, - /// Create a new task #[command(visible_alias = "add")] Create { @@ -265,3 +256,26 @@ pub enum LabelAction { /// List all distinct labels ListAll, } + +#[derive(Subcommand)] +pub enum ProjectAction { + /// Initialise a central project and bind the current directory to it + Init { + /// Project name + name: String, + }, + /// Bind the current directory to an existing project + Bind { + /// Project name + name: String, + }, + /// Remove the binding for the current directory + Unbind, + /// Delete a project from central storage and remove all directory bindings + Delete { + /// Project name + name: String, + }, + /// List all known projects in central storage + List, +} diff --git a/src/db.rs b/src/db.rs index c312665a854af63cb226fe462d2573c0f16fc0a3..8b22940a4f2a222ba01461c472ccaeea25adfbd7 100644 --- a/src/db.rs +++ b/src/db.rs @@ -248,7 +248,7 @@ impl Store { let base_path = project_dir.join(BASE_FILE); if !base_path.exists() { - bail!("project '{project}' is not initialized. Run 'td init {project}'"); + bail!("project '{project}' is not initialized. Run 'td project init {project}'"); } let base = fs::read(&base_path) @@ -561,7 +561,7 @@ pub fn use_project(cwd: &Path, project: &str) -> Result<()> { let root = data_root()?; validate_project_name(project)?; if !project_dir(&root, project).join(BASE_FILE).exists() { - bail!("project '{project}' not found. Run 'td projects' to list known projects"); + bail!("project '{project}' not found. Run 'td project list' to list known projects"); } bind_project(cwd, project) } @@ -571,7 +571,7 @@ pub fn open(start: &Path) -> Result { let explicit = std::env::var(PROJECT_ENV).ok(); let project = resolve_project_name(start, &root, explicit.as_deref())?.ok_or_else(|| { anyhow!( - "no project selected. Use --project/TD_PROJECT, run 'td use ', or run 'td init '" + "no project selected. Use --project/TD_PROJECT, run 'td project bind ', or run 'td project init '" ) })?; Store::open(&root, &project) @@ -725,6 +725,40 @@ fn resolve_project_name( Ok(None) } +pub fn unbind_project(cwd: &Path) -> Result<()> { + let root = data_root()?; + let canonical = canonicalize_binding_path(cwd)?; + let canonical_str = canonical.to_string_lossy().to_string(); + + let mut bindings = load_bindings(&root)?; + if !bindings.bindings.contains_key(&canonical_str) { + bail!("path '{}' is not bound to any project", canonical.display()); + } + bindings.bindings.remove(&canonical_str); + save_bindings(&root, &bindings) +} + +pub fn delete_project(name: &str) -> Result<()> { + validate_project_name(name)?; + let root = data_root()?; + let proj_dir = project_dir(&root, name); + + if !proj_dir.join(BASE_FILE).exists() { + bail!("project '{name}' not found"); + } + + fs::remove_dir_all(&proj_dir).with_context(|| { + format!( + "failed to remove project directory '{}'", + proj_dir.display() + ) + })?; + + let mut bindings = load_bindings(&root)?; + bindings.bindings.retain(|_, project| project != name); + save_bindings(&root, &bindings) +} + fn bind_project(cwd: &Path, project: &str) -> Result<()> { validate_project_name(project)?;