pub mod cli;
pub mod doc;
pub mod render;
pub mod search;
pub mod tests;
use anyhow::{bail, Result};
use cli::Command;
use doc::Documentation;
use forc_pkg as pkg;
use forc_pkg::{
manifest::{GenericManifestFile, ManifestFile},
PackageManifestFile,
};
use forc_tracing::println_action_green;
use forc_util::default_output_directory;
use render::RenderedDocumentation;
use std::{
fs,
path::{Path, PathBuf},
};
use sway_core::{language::ty::TyProgram, BuildTarget, Engines};
pub const ASSETS_DIR_NAME: &str = "static.files";
#[derive(Clone)]
pub struct RenderPlan<'e> {
no_deps: bool,
document_private_items: bool,
engines: &'e Engines,
}
impl<'e> RenderPlan<'e> {
pub fn new(
no_deps: bool,
document_private_items: bool,
engines: &'e Engines,
) -> RenderPlan<'e> {
Self {
no_deps,
document_private_items,
engines,
}
}
}
pub struct ProgramInfo<'a> {
pub ty_program: TyProgram,
pub engines: &'a Engines,
pub manifest: &'a ManifestFile,
pub pkg_manifest: &'a PackageManifestFile,
}
pub fn compile_html(
build_instructions: &Command,
get_doc_dir: &dyn Fn(&Command) -> String,
) -> Result<(PathBuf, Box<PackageManifestFile>)> {
let dir = if let Some(ref path) = build_instructions.path {
PathBuf::from(path)
} else {
std::env::current_dir()?
};
let manifest = ManifestFile::from_dir(dir)?;
let ManifestFile::Package(pkg_manifest) = &manifest else {
bail!("forc-doc does not support workspaces.")
};
let out_path = default_output_directory(manifest.dir());
let doc_dir = get_doc_dir(build_instructions);
let doc_path = out_path.join(doc_dir);
if doc_path.exists() {
std::fs::remove_dir_all(&doc_path)?;
}
fs::create_dir_all(&doc_path)?;
println_action_green(
"Compiling",
&format!(
"{} ({})",
pkg_manifest.project_name(),
manifest.dir().to_string_lossy()
),
);
let member_manifests = manifest.member_manifests()?;
let lock_path = manifest.lock_path()?;
let ipfs_node = build_instructions.ipfs_node.clone().unwrap_or_default();
let plan = pkg::BuildPlan::from_lock_and_manifests(
&lock_path,
&member_manifests,
build_instructions.locked,
build_instructions.offline,
&ipfs_node,
)?;
let engines = Engines::default();
let tests_enabled = build_instructions.document_private_items;
let mut compile_results = pkg::check(
&plan,
BuildTarget::default(),
build_instructions.silent,
None,
tests_enabled,
&engines,
None,
&build_instructions.experimental.experimental,
&build_instructions.experimental.no_experimental,
)?;
let raw_docs = if build_instructions.no_deps {
let Some(ty_program) = compile_results
.pop()
.and_then(|(programs, _handler)| programs)
.and_then(|p| p.typed.ok())
else {
bail! {
"documentation could not be built from manifest located at '{}'",
pkg_manifest.path().display()
}
};
let program_info = ProgramInfo {
ty_program,
engines: &engines,
manifest: &manifest,
pkg_manifest,
};
build_docs(program_info, &doc_path, build_instructions)?
} else {
let order = plan.compilation_order();
let graph = plan.graph();
let manifest_map = plan.manifest_map();
let mut raw_docs = Documentation(Vec::new());
for (node, (compile_result, _handler)) in order.iter().zip(compile_results) {
let id = &graph[*node].id();
if let Some(pkg_manifest_file) = manifest_map.get(id) {
let manifest_file = ManifestFile::from_dir(pkg_manifest_file.path())?;
let Some(ty_program) = compile_result.and_then(|programs| programs.typed.ok())
else {
bail!(
"documentation could not be built from manifest located at '{}'",
pkg_manifest_file.path().display()
)
};
let program_info = ProgramInfo {
ty_program,
engines: &engines,
manifest: &manifest_file,
pkg_manifest: pkg_manifest_file,
};
raw_docs
.0
.extend(build_docs(program_info, &doc_path, build_instructions)?.0);
}
}
raw_docs
};
search::write_search_index(&doc_path, &raw_docs)?;
Ok((doc_path, pkg_manifest.to_owned()))
}
fn build_docs(
program_info: ProgramInfo,
doc_path: &Path,
build_instructions: &Command,
) -> Result<Documentation> {
let Command {
document_private_items,
no_deps,
..
} = *build_instructions;
let ProgramInfo {
ty_program,
engines,
manifest,
pkg_manifest,
} = program_info;
println_action_green(
"Building",
&format!(
"documentation for {} ({})",
pkg_manifest.project_name(),
manifest.dir().to_string_lossy()
),
);
let raw_docs = Documentation::from_ty_program(
engines,
pkg_manifest.project_name(),
&ty_program,
document_private_items,
)?;
let root_attributes = (!ty_program.root_module.attributes.is_empty())
.then_some(ty_program.root_module.attributes);
let forc_version = pkg_manifest
.project
.forc_version
.as_ref()
.map(|ver| format!("Forc v{}.{}.{}", ver.major, ver.minor, ver.patch));
let rendered_docs = RenderedDocumentation::from_raw_docs(
raw_docs.clone(),
RenderPlan::new(no_deps, document_private_items, engines),
root_attributes,
&ty_program.kind,
forc_version,
)?;
write_content(rendered_docs, doc_path)?;
println_action_green("Finished", pkg_manifest.project_name());
Ok(raw_docs)
}
fn write_content(rendered_docs: RenderedDocumentation, doc_path: &Path) -> Result<()> {
for doc in rendered_docs.0 {
let mut doc_path = doc_path.to_path_buf();
for prefix in doc.module_info.module_prefixes {
doc_path.push(prefix);
}
fs::create_dir_all(&doc_path)?;
doc_path.push(doc.html_filename);
fs::write(&doc_path, doc.file_contents.0.as_bytes())?;
}
Ok(())
}
const DOC_DIR_NAME: &str = "doc";
pub fn get_doc_dir(_build_instructions: &Command) -> String {
DOC_DIR_NAME.into()
}