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