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;
8use foundry_common::compile::ProjectCompiler;
9use foundry_config::{Config, load_config_with_root};
10use std::{path::PathBuf, process::Command};
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() {
82 if let Ok(output) = Command::new("git").args(["remote", "get-url", "origin"]).output()
84 && !output.stdout.is_empty()
85 {
86 let remote = String::from_utf8(output.stdout)?.trim().to_owned();
87 if let Some(captures) = GH_REPO_PREFIX_REGEX.captures(&remote) {
88 let brand = captures.name("brand").unwrap().as_str();
89 let tld = captures.name("tld").unwrap().as_str();
90 let project = GH_REPO_PREFIX_REGEX.replace(&remote, "");
91 doc_config.repository =
92 Some(format!("https://{brand}.{tld}/{}", project.trim_end_matches(".git")));
93 }
94 }
95 }
96
97 let commit = foundry_cli::utils::Git::new(root).commit_hash(false, "HEAD").ok();
98
99 let mut builder = DocBuilder::new(
100 root.clone(),
101 project.paths.sources,
102 project.paths.libraries,
103 self.include_libraries,
104 )
105 .with_should_build(self.build)
106 .with_config(doc_config.clone())
107 .with_fmt(config.fmt)
108 .with_preprocessor(ContractInheritance { include_libraries: self.include_libraries })
109 .with_preprocessor(Inheritdoc::default())
110 .with_preprocessor(InferInlineHyperlinks::default())
111 .with_preprocessor(GitSource {
112 root: root.clone(),
113 commit,
114 repository: doc_config.repository.clone(),
115 });
116
117 if let Some(deployments) = self.deployments {
119 builder = builder.with_preprocessor(Deployments { root: root.clone(), deployments });
120 }
121
122 builder.build(compiler)?;
123
124 if self.serve {
125 Server::new(doc_config.out)
126 .with_hostname(self.hostname.unwrap_or_else(|| "localhost".into()))
127 .with_port(self.port.unwrap_or(3000))
128 .open(self.open)
129 .serve()?;
130 }
131
132 Ok(())
133 }
134
135 pub fn is_watch(&self) -> bool {
137 self.watch.watch.is_some()
138 }
139
140 pub fn config(&self) -> Result<Config> {
141 load_config_with_root(self.root.as_deref())
142 }
143}