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