forge/cmd/
cache.rs

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