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        for chain_or_all in chains {
61            match chain_or_all {
62                ChainOrAll::NamedChain(chain) => {
63                    clean_chain_cache(chain, blocks.clone(), etherscan)?
64                }
65                ChainOrAll::All => {
66                    let warnings = if etherscan {
67                        Config::clean_foundry_etherscan_cache()?
68                    } else {
69                        Config::clean_foundry_cache()?
70                    };
71                    for warning in warnings {
72                        let _ = sh_warn!("{warning}");
73                    }
74                }
75            }
76        }
77
78        Ok(())
79    }
80}
81
82#[derive(Debug, Parser)]
83pub struct LsArgs {
84    /// The chains to list the cache for.
85    ///
86    /// Can also be "all" to list all chains.
87    #[arg(
88        env = "CHAIN",
89        default_value = "all",
90        value_parser = ChainOrAllValueParser::default(),
91    )]
92    chains: Vec<ChainOrAll>,
93}
94
95impl LsArgs {
96    pub fn run(self) -> Result<()> {
97        let Self { chains } = self;
98        let mut cache = Cache::default();
99        for chain_or_all in chains {
100            match chain_or_all {
101                ChainOrAll::NamedChain(chain) => {
102                    cache.chains.push(Config::list_foundry_chain_cache(chain.into())?)
103                }
104                ChainOrAll::All => cache = Config::list_foundry_cache()?,
105            }
106        }
107        sh_eprint!("{cache}")?;
108        Ok(())
109    }
110}
111
112#[derive(Clone, Debug)]
113pub enum ChainOrAll {
114    NamedChain(NamedChain),
115    All,
116}
117
118impl FromStr for ChainOrAll {
119    type Err = String;
120
121    fn from_str(s: &str) -> Result<Self, Self::Err> {
122        if let Ok(chain) = NamedChain::from_str(s) {
123            Ok(Self::NamedChain(chain))
124        } else if s == "all" {
125            Ok(Self::All)
126        } else {
127            Err(format!("Expected known chain or all, found: {s}"))
128        }
129    }
130}
131
132fn clean_chain_cache(chain: impl Into<Chain>, blocks: Vec<u64>, etherscan: bool) -> Result<()> {
133    let chain = chain.into();
134    let mut warnings = Vec::new();
135    if blocks.is_empty() {
136        warnings.extend(Config::clean_foundry_etherscan_chain_cache(chain)?);
137        if etherscan {
138            for warning in warnings {
139                let _ = sh_warn!("{warning}");
140            }
141            return Ok(());
142        }
143        warnings.extend(Config::clean_foundry_chain_cache(chain)?);
144    } else {
145        for block in blocks {
146            warnings.extend(Config::clean_foundry_block_cache(chain, block)?);
147        }
148    }
149    for warning in warnings {
150        let _ = sh_warn!("{warning}");
151    }
152    Ok(())
153}
154
155/// The value parser for `ChainOrAll`
156#[derive(Clone, Debug)]
157pub struct ChainOrAllValueParser {
158    inner: PossibleValuesParser,
159}
160
161impl Default for ChainOrAllValueParser {
162    fn default() -> Self {
163        Self { inner: possible_chains() }
164    }
165}
166
167impl TypedValueParser for ChainOrAllValueParser {
168    type Value = ChainOrAll;
169
170    fn parse_ref(
171        &self,
172        cmd: &Command,
173        arg: Option<&Arg>,
174        value: &OsStr,
175    ) -> Result<Self::Value, clap::Error> {
176        self.inner.parse_ref(cmd, arg, value)?.parse::<ChainOrAll>().map_err(|_| {
177            clap::Error::raw(
178                clap::error::ErrorKind::InvalidValue,
179                "chain argument did not match any possible chain variant",
180            )
181        })
182    }
183}
184
185fn possible_chains() -> PossibleValuesParser {
186    Some(&"all").into_iter().chain(NamedChain::VARIANT_NAMES).into()
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn can_parse_cache_ls() {
195        let args: CacheArgs = CacheArgs::parse_from(["cache", "ls"]);
196        assert!(matches!(args.sub, CacheSubcommands::Ls(_)));
197    }
198}