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