use crate::{
doc::{Document, Documentation},
render::{constant::INDEX_FILENAME, RenderedDocumentation},
};
use anyhow::{bail, Result};
use clap::Parser;
use cli::Command;
use forc_pkg as pkg;
use forc_util::default_output_directory;
use include_dir::{include_dir, Dir};
use pkg::manifest::ManifestFile;
use std::{
process::Command as Process,
sync::Arc,
{fs, path::PathBuf},
};
use sway_core::{decl_engine::DeclEngine, BuildTarget, Engines, TypeEngine};
mod cli;
mod doc;
mod render;
#[derive(Clone)]
struct RenderPlan {
document_private_items: bool,
type_engine: Arc<TypeEngine>,
decl_engine: Arc<DeclEngine>,
}
impl RenderPlan {
fn new(
document_private_items: bool,
type_engine: Arc<TypeEngine>,
decl_engine: Arc<DeclEngine>,
) -> RenderPlan {
Self {
document_private_items,
type_engine,
decl_engine,
}
}
}
pub fn main() -> Result<()> {
let Command {
manifest_path,
document_private_items,
open: open_result,
offline,
silent,
locked,
no_deps,
} = Command::parse();
let dir = if let Some(ref path) = manifest_path {
PathBuf::from(path)
} else {
std::env::current_dir()?
};
let manifest = ManifestFile::from_dir(&dir)?;
let pkg_manifest = if let ManifestFile::Package(pkg_manifest) = &manifest {
pkg_manifest
} else {
bail!("forc-doc does not support workspaces.")
};
const DOC_DIR_NAME: &str = "doc";
let project_name = &pkg_manifest.project.name;
let out_path = default_output_directory(manifest.dir());
let doc_path = out_path.join(DOC_DIR_NAME);
fs::create_dir_all(&doc_path)?;
let member_manifests = manifest.member_manifests()?;
let lock_path = manifest.lock_path()?;
let plan =
pkg::BuildPlan::from_lock_and_manifests(&lock_path, &member_manifests, locked, offline)?;
let type_engine = TypeEngine::default();
let decl_engine = DeclEngine::default();
let engines = Engines::new(&type_engine, &decl_engine);
let tests_enabled = true;
let typed_program = match pkg::check(
&plan,
BuildTarget::default(),
silent,
tests_enabled,
engines,
)?
.pop()
.and_then(|compilation| compilation.value)
.and_then(|programs| programs.typed)
{
Some(typed_program) => typed_program,
_ => bail!("CompileResult returned None"),
};
let raw_docs: Documentation = Document::from_ty_program(
&decl_engine,
project_name,
&typed_program,
no_deps,
document_private_items,
)?;
let root_attributes =
(!typed_program.root.attributes.is_empty()).then_some(typed_program.root.attributes);
let program_kind = typed_program.kind;
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,
RenderPlan::new(
document_private_items,
Arc::from(type_engine),
Arc::from(decl_engine),
),
root_attributes,
program_kind,
forc_version,
)?;
for doc in rendered_docs.0 {
let mut doc_path = doc_path.clone();
for prefix in doc.module_info.module_prefixes {
if &prefix != project_name {
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())?;
}
static ASSETS_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/src/assets");
const ASSETS_DIR_NAME: &str = "assets";
let assets_path = doc_path.join(ASSETS_DIR_NAME);
fs::create_dir_all(&assets_path)?;
for file in ASSETS_DIR.files() {
let asset_path = assets_path.join(file.path());
fs::write(asset_path, file.contents())?;
}
const SWAY_HJS_FILENAME: &str = "highlight.js";
let sway_hjs = std::include_bytes!("assets/highlight.js");
fs::write(assets_path.join(SWAY_HJS_FILENAME), sway_hjs)?;
if open_result {
const BROWSER_ENV_VAR: &str = "BROWSER";
let path = doc_path.join(INDEX_FILENAME);
let default_browser_opt = std::env::var_os(BROWSER_ENV_VAR);
match default_browser_opt {
Some(def_browser) => {
let browser = PathBuf::from(def_browser);
if let Err(e) = Process::new(&browser).arg(path).status() {
bail!(
"Couldn't open docs with {}: {}",
browser.to_string_lossy(),
e
);
}
}
None => {
if let Err(e) = opener::open(&path) {
bail!("Couldn't open docs: {}", e);
}
}
}
}
Ok(())
}