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#[derive(Debug, Parser)]
13pub struct CacheArgs {
14 #[command(subcommand)]
15 pub sub: CacheSubcommands,
16}
17
18#[derive(Debug, Subcommand)]
19pub enum CacheSubcommands {
20 Clean(CleanArgs),
22
23 Ls(LsArgs),
25}
26
27#[derive(Debug, Parser)]
29#[command(group = clap::ArgGroup::new("etherscan-blocks").multiple(false))]
30pub struct CleanArgs {
31 #[arg(
35 env = "CHAIN",
36 default_value = "all",
37 value_parser = ChainOrAllValueParser::default(),
38 )]
39 chains: Vec<ChainOrAll>,
40
41 #[arg(
43 short,
44 long,
45 num_args(1..),
46 value_delimiter(','),
47 group = "etherscan-blocks"
48 )]
49 blocks: Vec<u64>,
50
51 #[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 #[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#[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}