1use super::watch::WatchArgs;
2use clap::{Parser, ValueHint};
3use eyre::Result;
4use forge_doc::{
5 ContractInheritance, Deployments, DocBuilder, GitSource, InferInlineHyperlinks, Inheritdoc,
6};
7use foundry_cli::{opts::GH_REPO_PREFIX_REGEX, utils::Git};
8use foundry_common::compile::ProjectCompiler;
9use foundry_config::{Config, load_config_with_root};
10use std::path::PathBuf;
11
12mod server;
13use server::Server;
14
15#[derive(Clone, Debug, Parser)]
16pub struct DocArgs {
17 #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
22 pub root: Option<PathBuf>,
23
24 #[arg(
28 long,
29 short,
30 value_hint = ValueHint::DirPath,
31 value_name = "PATH",
32 )]
33 out: Option<PathBuf>,
34
35 #[arg(long, short)]
37 build: bool,
38
39 #[arg(long, short)]
41 serve: bool,
42
43 #[arg(long, requires = "serve")]
45 open: bool,
46
47 #[arg(long, requires = "serve")]
49 hostname: Option<String>,
50
51 #[command(flatten)]
52 pub watch: WatchArgs,
53
54 #[arg(long, short, requires = "serve")]
56 port: Option<usize>,
57
58 #[arg(long)]
61 deployments: Option<Option<PathBuf>>,
62
63 #[arg(long, short)]
65 include_libraries: bool,
66}
67
68impl DocArgs {
69 pub async fn run(self) -> Result<()> {
70 let config = self.config()?;
71 let root = &config.root;
72 let project = config.project()?;
73 let compiler = ProjectCompiler::new().quiet(true);
74 let mut output = compiler.compile(&project)?;
75 let compiler = output.parser_mut().solc_mut().compiler_mut();
76
77 let mut doc_config = config.doc;
78 if let Some(out) = self.out {
79 doc_config.out = out;
80 }
81 if doc_config.repository.is_none()
83 && let Some(remote) = Git::new(root).remote_url("origin")
84 && let Some(captures) = GH_REPO_PREFIX_REGEX.captures(&remote)
85 {
86 let brand = captures.name("brand").unwrap().as_str();
87 let tld = captures.name("tld").unwrap().as_str();
88 let project = GH_REPO_PREFIX_REGEX.replace(&remote, "");
89 doc_config.repository =
90 Some(format!("https://{brand}.{tld}/{}", project.trim_end_matches(".git")));
91 }
92
93 let commit = Git::new(root).commit_hash(false, "HEAD").ok();
94
95 let mut builder = DocBuilder::new(
96 root.clone(),
97 project.paths.sources,
98 project.paths.libraries,
99 self.include_libraries,
100 )
101 .with_should_build(self.build)
102 .with_config(doc_config.clone())
103 .with_fmt(config.fmt)
104 .with_preprocessor(ContractInheritance { include_libraries: self.include_libraries })
105 .with_preprocessor(Inheritdoc::default())
106 .with_preprocessor(InferInlineHyperlinks::default())
107 .with_preprocessor(GitSource {
108 root: root.clone(),
109 commit,
110 repository: doc_config.repository.clone(),
111 });
112
113 if let Some(deployments) = self.deployments {
115 builder = builder.with_preprocessor(Deployments { root: root.clone(), deployments });
116 }
117
118 builder.build(compiler)?;
119
120 if self.serve {
121 Server::new(doc_config.out)
122 .with_hostname(self.hostname.unwrap_or_else(|| "localhost".into()))
123 .with_port(self.port.unwrap_or(3000))
124 .open(self.open)
125 .serve()?;
126 }
127
128 Ok(())
129 }
130
131 pub const fn is_watch(&self) -> bool {
133 self.watch.watch.is_some()
134 }
135
136 pub fn config(&self) -> Result<Config> {
137 load_config_with_root(self.root.as_deref())
138 }
139}