workspacer_cli/
prune.rs

1// ---------------- [ File: workspacer-cli/src/prune.rs ]
2crate::ix!();
3
4/// The top-level `prune` subcommand has two variants:
5///   - **Crate** => `ws prune crate --crate <NAME>` => calls `prune_invalid_category_slugs()` on that single crate
6///   - **Workspace** => `ws prune workspace [--path <DIR>]` => calls `prune_invalid_category_slugs_from_members()` on the entire workspace
7#[derive(Debug, StructOpt)]
8pub enum PruneSubcommand {
9    /// Prune categories/keywords in a single crate
10    #[structopt(name = "crate")]
11    Crate(PruneCrateCommand),
12
13    /// Prune categories/keywords in the entire workspace
14    #[structopt(name = "workspace")]
15    Workspace(PruneWorkspaceCommand),
16}
17
18impl PruneSubcommand {
19    /// Entrypoint for `ws prune ...`
20    pub async fn run(&self) -> Result<(), WorkspaceError> {
21        match self {
22            PruneSubcommand::Crate(cmd) => cmd.run().await,
23            PruneSubcommand::Workspace(cmd) => cmd.run().await,
24        }
25    }
26}
27
28/// Subcommand data for `ws prune crate --crate <NAME> [--workspace <dir>] [--skip-git-check]`
29#[derive(Debug, StructOpt, Getters, Setters)]
30#[getset(get="pub")]
31pub struct PruneCrateCommand {
32    /// The name (or path) of the crate
33    #[structopt(long = "crate")]
34    crate_name: String,
35
36    /// Optional path to the workspace root
37    #[structopt(long = "workspace")]
38    workspace_path: Option<PathBuf>,
39
40    /// If true, skip checking for a clean Git repo
41    #[structopt(long = "skip-git-check")]
42    skip_git_check: bool,
43}
44
45impl PruneCrateCommand {
46    pub async fn run(&self) -> Result<(), WorkspaceError> {
47        let crate_name_owned = self.crate_name().clone();
48
49        // Use the existing helper that loads the workspace, 
50        // finds the crate, checks Git cleanliness (if not skipped), etc.
51        run_with_workspace_and_crate_name(
52            self.workspace_path().clone(),
53            *self.skip_git_check(),
54            crate_name_owned,
55            |ws, crate_name_str| {
56                Box::pin(async move {
57                    // 1) find the crate
58                    let arc_crate = ws.find_crate_by_name(crate_name_str).await
59                        .ok_or_else(|| {
60                            error!("No crate named '{}' found in workspace", crate_name_str);
61                            CrateError::CrateNotFoundInWorkspace {
62                                crate_name: crate_name_str.to_string(),
63                            }
64                        })?;
65                    // 2) Lock the crate handle
66                    let mut guard = arc_crate.lock().await;
67                    // 3) Call `prune_invalid_category_slugs` on this one crate
68                    let removed = guard.prune_invalid_category_slugs().await?;
69                    
70                    info!("Prune done for crate='{}': removed {} invalid category/keyword entries.", crate_name_str, removed);
71                    println!("Removed {} invalid category or keyword entries from crate='{}'.", removed, crate_name_str);
72                    Ok(())
73                })
74            },
75        )
76        .await
77    }
78}
79
80/// Subcommand data for `ws prune workspace [--path <DIR>] [--skip-git-check]`
81#[derive(Debug, StructOpt, Getters, Setters)]
82#[getset(get="pub")]
83pub struct PruneWorkspaceCommand {
84    /// If provided, we use this path as the workspace root
85    #[structopt(long = "path")]
86    workspace_path: Option<PathBuf>,
87
88    /// If true, skip the Git clean check
89    #[structopt(long = "skip-git-check")]
90    skip_git_check: bool,
91}
92
93impl PruneWorkspaceCommand {
94    pub async fn run(&self) -> Result<(), WorkspaceError> {
95        // We'll do the same approach as e.g. FormatAllImportsCommand
96        // => load the workspace, optionally ensure Git is clean, then call the trait.
97        let ws_path = self.workspace_path.clone();
98        let skip_flag = self.skip_git_check;
99
100        run_with_workspace(ws_path, skip_flag, move |ws| {
101            Box::pin(async move {
102                // The `PruneInvalidCategorySlugsFromSubstructures` trait is implemented for Workspace
103                let removed_total = ws.prune_invalid_category_slugs_from_members().await?;
104                info!("Successfully pruned categories in the entire workspace, total removed={}", removed_total);
105                println!("Removed {} invalid category/keyword entries across the entire workspace.", removed_total);
106                Ok(())
107            })
108        })
109        .await
110    }
111}