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