1use crate::cmd::{install, watch::WatchArgs};
4use clap::{Parser, ValueHint};
5use eyre::Result;
6use forge_doc::DocBuilder;
7use foundry_cli::{opts::GH_REPO_PREFIX_REGEX, utils::Git};
8use foundry_common::{compile::ProjectCompiler, shell};
9use foundry_config::{Config, load_config_with_root};
10use std::{path::PathBuf, time::Instant};
11
12#[derive(Clone, Debug, Parser)]
13pub struct DocArgs {
14 #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
19 pub root: Option<PathBuf>,
20
21 #[arg(long, short, value_hint = ValueHint::DirPath, value_name = "PATH")]
28 out: Option<PathBuf>,
29
30 #[arg(long, short)]
32 pub include_libraries: bool,
33
34 #[arg(long, value_name = "PATH", num_args(0..=1))]
39 pub deployments: Option<Option<PathBuf>>,
40
41 #[command(flatten)]
42 pub watch: WatchArgs,
43
44 #[arg(long, hide = true)]
46 serve: bool,
47}
48
49impl DocArgs {
50 pub async fn run(self) -> Result<()> {
51 if self.serve {
52 eyre::bail!(
53 "`--serve` has been removed. Generate the docs with `forge doc`, \
54 then run `npm run dev` from the generated docs directory."
55 );
56 }
57 let mut config = self.config()?;
58
59 if install::install_missing_dependencies(&mut config).await && config.auto_detect_remappings
60 {
61 config = self.config()?;
63 }
64
65 let root = &config.root;
66 let project = config.project()?;
67
68 let mut output = ProjectCompiler::new().compile(&project)?;
73 let compiler = output.parser_mut().solc_mut().compiler_mut();
74
75 let mut doc_cfg = config.doc;
76 if let Some(out) = self.out.clone() {
77 doc_cfg.out = out;
78 }
79
80 if doc_cfg.repository.is_none()
82 && let Some(remote) = Git::new(root).remote_url("origin")
83 && let Some(captures) = GH_REPO_PREFIX_REGEX.captures(&remote)
84 {
85 let brand = captures.name("brand").unwrap().as_str();
86 let tld = captures.name("tld").unwrap().as_str();
87 let project_path = GH_REPO_PREFIX_REGEX.replace(&remote, "");
88 doc_cfg.repository =
89 Some(format!("https://{brand}.{tld}/{}", project_path.trim_end_matches(".git")));
90 }
91
92 let git = Git::new(root);
93 let commit = git.commit_hash(false, "HEAD").ok();
94 let branch = git.current_rev_branch(root).ok().map(|(_, b)| b).filter(|b| b != "HEAD");
97
98 let mut builder = DocBuilder::new(
99 root.clone(),
100 project.paths.sources,
101 project.paths.libraries,
102 self.include_libraries,
103 );
104 builder.commit = commit;
105 builder.branch = branch;
106 builder.deployments = self.deployments;
107 builder.config = doc_cfg.clone();
108
109 let out_dir = if doc_cfg.out.is_absolute() { doc_cfg.out } else { root.join(&doc_cfg.out) };
110
111 if !shell::is_quiet() {
112 sh_println!("Generating documentation...")?;
113 }
114 let started = Instant::now();
115 let stats = builder.build(compiler)?;
116 let elapsed = started.elapsed();
117
118 if !shell::is_quiet() {
119 sh_println!(
120 "Generated {pages} page{ps} from {sources} source{ss} in {elapsed:.2?} (render {render:.2?}, site {site:.2?})",
121 pages = stats.pages,
122 ps = if stats.pages <= 1 { "" } else { "s" },
123 sources = stats.sources,
124 ss = if stats.sources <= 1 { "" } else { "s" },
125 elapsed = elapsed,
126 render = stats.render_elapsed,
127 site = stats.site_elapsed,
128 )?;
129
130 sh_println!(
133 "\nDocumentation written to: {}\n\nTo preview:\n cd {}\n npm install --legacy-peer-deps\n npm run dev",
134 out_dir.display(),
135 out_dir.display(),
136 )?;
137 }
138
139 Ok(())
140 }
141
142 pub const fn is_watch(&self) -> bool {
144 self.watch.watch.is_some()
145 }
146
147 pub fn config(&self) -> Result<Config> {
148 load_config_with_root(self.root.as_deref())
149 }
150}