Skip to main content

forge/cmd/
cache.rs

1use cache::Cache;
2use clap::{
3    Arg, Command, Parser, Subcommand,
4    builder::{PossibleValuesParser, TypedValueParser},
5};
6use eyre::Result;
7use foundry_common::sh_warn;
8use foundry_config::{Chain, Config, NamedChain, cache};
9use std::{ffi::OsStr, str::FromStr};
10
11/// CLI arguments for `forge cache`.
12#[derive(Debug, Parser)]
13pub struct CacheArgs {
14    #[command(subcommand)]
15    pub sub: CacheSubcommands,
16}
17
18#[derive(Debug, Subcommand)]
19pub enum CacheSubcommands {
20    /// Cleans cached data from the global foundry directory.
21    Clean(CleanArgs),
22
23    /// Shows cached data from the global foundry directory.
24    Ls(LsArgs),
25}
26
27/// CLI arguments for `forge clean`.
28#[derive(Debug, Parser)]
29#[command(group = clap::ArgGroup::new("etherscan-blocks").multiple(false))]
30pub struct CleanArgs {
31    /// The chains to clean the cache for.
32    ///
33    /// Can also be "all" to clean all chains.
34    #[arg(
35        env = "CHAIN",
36        default_value = "all",
37        value_parser = ChainOrAllValueParser::default(),
38    )]
39    chains: Vec<ChainOrAll>,
40
41    /// The blocks to clean the cache for.
42    #[arg(
43        short,
44        long,
45        num_args(1..),
46        value_delimiter(','),
47        group = "etherscan-blocks"
48    )]
49    blocks: Vec<u64>,
50
51    /// Whether to clean the Etherscan cache.
52    #[arg(long, group = "etherscan-blocks")]
53    etherscan: bool,
54}
55
56impl CleanArgs {
57    pub fn run(self) -> Result<()> {
58        let Self { chains, blocks, etherscan } = self;
59
60        eyre::ensure!(
61            blocks.is_empty() || !chains.iter().any(|chain| matches!(chain, ChainOrAll::All)),
62            "--blocks requires explicit chains and cannot be used with 'all'"
63        );
64
65        for chain_or_all in chains {
66            match chain_or_all {
67                ChainOrAll::NamedChain(chain) => {
68                    clean_chain_cache(chain, blocks.clone(), etherscan)?
69                }
70                ChainOrAll::All => {
71                    let warnings = if etherscan {
72                        Config::clean_foundry_etherscan_cache()?
73                    } else {
74                        Config::clean_foundry_cache()?
75                    };
76                    for warning in warnings {
77                        let _ = sh_warn!("{warning}");
78                    }
79                }
80            }
81        }
82
83        Ok(())
84    }
85}
86
87#[derive(Debug, Parser)]
88pub struct LsArgs {
89    /// The chains to list the cache for.
90    ///
91    /// Can also be "all" to list all chains.
92    #[arg(
93        env = "CHAIN",
94        default_value = "all",
95        value_parser = ChainOrAllValueParser::default(),
96    )]
97    chains: Vec<ChainOrAll>,
98}
99
100impl LsArgs {
101    pub fn run(self) -> Result<()> {
102        let Self { chains } = self;
103        let mut cache = Cache::default();
104        for chain_or_all in chains {
105            match chain_or_all {
106                ChainOrAll::NamedChain(chain) => {
107                    cache.chains.push(Config::list_foundry_chain_cache(chain.into())?)
108                }
109                ChainOrAll::All => cache = Config::list_foundry_cache()?,
110            }
111        }
112        sh_eprint!("{cache}")?;
113        Ok(())
114    }
115}
116
117#[derive(Clone, Debug)]
118pub enum ChainOrAll {
119    NamedChain(NamedChain),
120    All,
121}
122
123impl FromStr for ChainOrAll {
124    type Err = String;
125
126    fn from_str(s: &str) -> Result<Self, Self::Err> {
127        if let Ok(chain) = NamedChain::from_str(s) {
128            Ok(Self::NamedChain(chain))
129        } else if s == "all" {
130            Ok(Self::All)
131        } else {
132            Err(format!("Expected known chain or all, found: {s}"))
133        }
134    }
135}
136
137fn clean_chain_cache(chain: impl Into<Chain>, blocks: Vec<u64>, etherscan: bool) -> Result<()> {
138    let chain = chain.into();
139    let mut warnings = Vec::new();
140    if blocks.is_empty() {
141        warnings.extend(Config::clean_foundry_etherscan_chain_cache(chain)?);
142        if etherscan {
143            for warning in warnings {
144                let _ = sh_warn!("{warning}");
145            }
146            return Ok(());
147        }
148        warnings.extend(Config::clean_foundry_chain_cache(chain)?);
149    } else {
150        for block in blocks {
151            warnings.extend(Config::clean_foundry_block_cache(chain, block)?);
152        }
153    }
154    for warning in warnings {
155        let _ = sh_warn!("{warning}");
156    }
157    Ok(())
158}
159
160/// The value parser for `ChainOrAll`
161#[derive(Clone, Debug)]
162pub struct ChainOrAllValueParser {
163    inner: PossibleValuesParser,
164}
165
166impl Default for ChainOrAllValueParser {
167    fn default() -> Self {
168        let inner = Some(&"all").into_iter().chain(NamedChain::VARIANT_NAMES).into();
169        Self { inner }
170    }
171}
172
173impl TypedValueParser for ChainOrAllValueParser {
174    type Value = ChainOrAll;
175
176    fn parse_ref(
177        &self,
178        cmd: &Command,
179        arg: Option<&Arg>,
180        value: &OsStr,
181    ) -> Result<Self::Value, clap::Error> {
182        self.inner.parse_ref(cmd, arg, value)?.parse::<ChainOrAll>().map_err(|_| {
183            clap::Error::raw(
184                clap::error::ErrorKind::InvalidValue,
185                "chain argument did not match any possible chain variant",
186            )
187        })
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn can_parse_cache_ls() {
197        let args: CacheArgs = CacheArgs::parse_from(["cache", "ls"]);
198        assert!(matches!(args.sub, CacheSubcommands::Ls(_)));
199    }
200}