1pub mod cli;
2pub mod doc;
3pub mod render;
4pub mod search;
5pub mod tests;
6
7use anyhow::{bail, Result};
8use cli::Command;
9use doc::Documentation;
10use forc_pkg as pkg;
11use forc_pkg::{
12 manifest::{GenericManifestFile, ManifestFile},
13 PackageManifestFile,
14};
15use forc_tracing::println_action_green;
16use forc_util::default_output_directory;
17use render::RenderedDocumentation;
18use std::sync::Arc;
19use std::{
20 fs,
21 path::{Path, PathBuf},
22};
23use sway_core::{language::ty::TyProgram, BuildTarget, Engines};
24use sway_features::ExperimentalFeatures;
25
26pub const ASSETS_DIR_NAME: &str = "static.files";
27
28#[derive(Clone)]
30pub struct RenderPlan<'e> {
31 no_deps: bool,
32 document_private_items: bool,
33 engines: &'e Engines,
34}
35
36impl<'e> RenderPlan<'e> {
37 pub fn new(
38 no_deps: bool,
39 document_private_items: bool,
40 engines: &'e Engines,
41 ) -> RenderPlan<'e> {
42 Self {
43 no_deps,
44 document_private_items,
45 engines,
46 }
47 }
48}
49
50pub struct ProgramInfo<'a> {
51 pub ty_program: Arc<TyProgram>,
52 pub engines: &'a Engines,
53 pub manifest: &'a ManifestFile,
54 pub pkg_manifest: &'a PackageManifestFile,
55}
56
57pub fn compile_html(
58 build_instructions: &Command,
59 get_doc_dir: &dyn Fn(&Command) -> String,
60) -> Result<(PathBuf, Box<PackageManifestFile>)> {
61 let dir = if let Some(ref path) = build_instructions.path {
63 PathBuf::from(path)
64 } else {
65 std::env::current_dir()?
66 };
67 let manifest = ManifestFile::from_dir(dir)?;
68 let ManifestFile::Package(pkg_manifest) = &manifest else {
69 bail!("forc-doc does not support workspaces.")
70 };
71
72 let out_path = default_output_directory(manifest.dir());
74 let doc_dir = get_doc_dir(build_instructions);
75 let doc_path = out_path.join(doc_dir);
76 if doc_path.exists() {
77 std::fs::remove_dir_all(&doc_path)?;
78 }
79 fs::create_dir_all(&doc_path)?;
80
81 println_action_green(
82 "Compiling",
83 &format!(
84 "{} ({})",
85 pkg_manifest.project_name(),
86 manifest.dir().to_string_lossy()
87 ),
88 );
89
90 let member_manifests = manifest.member_manifests()?;
91 let lock_path = manifest.lock_path()?;
92
93 let ipfs_node = build_instructions.ipfs_node.clone().unwrap_or_default();
94 let plan = pkg::BuildPlan::from_lock_and_manifests(
95 &lock_path,
96 &member_manifests,
97 build_instructions.locked,
98 build_instructions.offline,
99 &ipfs_node,
100 )?;
101
102 let engines = Engines::default();
103 let tests_enabled = build_instructions.document_private_items;
104 let mut compile_results = pkg::check(
105 &plan,
106 BuildTarget::default(),
107 build_instructions.silent,
108 None,
109 tests_enabled,
110 &engines,
111 None,
112 &build_instructions.experimental.experimental,
113 &build_instructions.experimental.no_experimental,
114 sway_core::DbgGeneration::Full,
115 )?;
116
117 let raw_docs = if build_instructions.no_deps {
118 let Some(ty_program) = compile_results
119 .pop()
120 .and_then(|(programs, _handler)| programs)
121 .and_then(|p| p.typed.ok())
122 else {
123 bail! {
124 "documentation could not be built from manifest located at '{}'",
125 pkg_manifest.path().display()
126 }
127 };
128 let program_info = ProgramInfo {
129 ty_program,
130 engines: &engines,
131 manifest: &manifest,
132 pkg_manifest,
133 };
134 build_docs(program_info, &doc_path, build_instructions)?
135 } else {
136 let order = plan.compilation_order();
137 let graph = plan.graph();
138 let manifest_map = plan.manifest_map();
139 let mut raw_docs = Documentation(Vec::new());
140
141 for (node, (compile_result, _handler)) in order.iter().zip(compile_results) {
142 let id = &graph[*node].id();
143 if let Some(pkg_manifest_file) = manifest_map.get(id) {
144 let manifest_file = ManifestFile::from_dir(pkg_manifest_file.path())?;
145 let Some(ty_program) = compile_result.and_then(|programs| programs.typed.ok())
146 else {
147 bail!(
148 "documentation could not be built from manifest located at '{}'",
149 pkg_manifest_file.path().display()
150 )
151 };
152 let program_info = ProgramInfo {
153 ty_program,
154 engines: &engines,
155 manifest: &manifest_file,
156 pkg_manifest: pkg_manifest_file,
157 };
158 raw_docs
159 .0
160 .extend(build_docs(program_info, &doc_path, build_instructions)?.0);
161 }
162 }
163 raw_docs
164 };
165 search::write_search_index(&doc_path, &raw_docs)?;
166
167 Ok((doc_path, pkg_manifest.to_owned()))
168}
169
170fn build_docs(
171 program_info: ProgramInfo,
172 doc_path: &Path,
173 build_instructions: &Command,
174) -> Result<Documentation> {
175 let Command {
176 document_private_items,
177 no_deps,
178 experimental,
179 ..
180 } = build_instructions;
181 let ProgramInfo {
182 ty_program,
183 engines,
184 manifest,
185 pkg_manifest,
186 } = program_info;
187
188 let experimental = ExperimentalFeatures::new(
189 &pkg_manifest.project.experimental,
190 &experimental.experimental,
191 &experimental.no_experimental,
192 )
193 .map_err(|err| anyhow::anyhow!("{err}"))?;
194
195 println_action_green(
196 "Building",
197 &format!(
198 "documentation for {} ({})",
199 pkg_manifest.project_name(),
200 manifest.dir().to_string_lossy()
201 ),
202 );
203
204 let raw_docs = Documentation::from_ty_program(
205 engines,
206 pkg_manifest.project_name(),
207 &ty_program,
208 *document_private_items,
209 experimental,
210 )?;
211 let root_attributes = (!ty_program.root_module.attributes.is_empty())
212 .then_some(ty_program.root_module.attributes.clone());
213 let forc_version = pkg_manifest
214 .project
215 .forc_version
216 .as_ref()
217 .map(|ver| format!("Forc v{}.{}.{}", ver.major, ver.minor, ver.patch));
218 let rendered_docs = RenderedDocumentation::from_raw_docs(
220 raw_docs.clone(),
221 RenderPlan::new(*no_deps, *document_private_items, engines),
222 root_attributes,
223 &ty_program.kind,
224 forc_version,
225 )?;
226
227 write_content(rendered_docs, doc_path)?;
229 println_action_green("Finished", pkg_manifest.project_name());
230
231 Ok(raw_docs)
232}
233
234fn write_content(rendered_docs: RenderedDocumentation, doc_path: &Path) -> Result<()> {
235 for doc in rendered_docs.0 {
236 let mut doc_path = doc_path.to_path_buf();
237 for prefix in doc.module_info.module_prefixes {
238 doc_path.push(prefix);
239 }
240 fs::create_dir_all(&doc_path)?;
241 doc_path.push(doc.html_filename);
242 fs::write(&doc_path, doc.file_contents.0.as_bytes())?;
243 }
244 Ok(())
245}
246
247const DOC_DIR_NAME: &str = "doc";
248pub fn get_doc_dir(_build_instructions: &Command) -> String {
249 DOC_DIR_NAME.into()
250}