pub(crate) mod directives;
mod formats;
mod nodes;
mod utils;
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use serde::Deserialize;
use crate::directives::{CrateDirective, DirectiveVisibility, ExecutableDirective};
use crate::formats::Format;
use crate::utils::FileTopLevelDirective;
pub(crate) use crate::utils::{check_for_manifest, SourceCodeFile};
#[derive(Clone, Debug, Deserialize)]
pub struct Configuration {
crate_name: String,
crate_dir: PathBuf,
doc_dir: PathBuf,
#[serde(default)]
force: bool,
#[serde(default)]
format: Format,
#[serde(default)]
visibility: DirectiveVisibility,
strip_src: bool,
}
impl Configuration {
fn get_canonical_crate_dir(&self) -> PathBuf {
let crate_dir = match self.crate_dir.canonicalize() {
Ok(d) => d,
Err(e) => panic!("Could not find directory {}", e),
};
if !crate_dir.is_dir() {
panic!("{} is not a directory", crate_dir.to_str().unwrap());
}
crate_dir
}
}
pub(crate) struct RuntimeConfiguration {
crate_name: String,
crate_dir: PathBuf,
src_dir: Option<PathBuf>,
doc_dir: PathBuf,
force: bool,
format: Format,
max_visibility: DirectiveVisibility,
executables: Vec<SourceCodeFile>,
lib: Option<SourceCodeFile>,
}
impl RuntimeConfiguration {
pub(crate) fn get_doc_file_name(&self, source_file_path: &Path) -> PathBuf {
let rel_path = source_file_path
.strip_prefix(self.src_dir.as_ref().unwrap_or(&self.crate_dir))
.unwrap_or(source_file_path);
if rel_path.ends_with("mod.rs") {
rel_path.parent().unwrap().to_owned()
}
else {
rel_path
.parent()
.unwrap()
.join(rel_path.file_stem().unwrap())
}
}
fn write_doc_file(
&self,
source_file_path: &Path,
file_top_level_directive: impl FileTopLevelDirective,
) {
let mut doc_file = self.doc_dir.join(file_top_level_directive.get_doc_file());
doc_file.set_extension(self.format.extension());
create_dir_all(doc_file.parent().unwrap()).unwrap();
if self.force
|| !doc_file.exists()
|| doc_file.metadata().unwrap().modified().unwrap()
< source_file_path.metadata().unwrap().modified().unwrap()
{
log::debug!("Writing docs to file {}", doc_file.to_str().unwrap());
let mut doc_file = File::create(doc_file).unwrap();
for line in file_top_level_directive.get_text(&self.format, &self.max_visibility) {
writeln!(&mut doc_file, "{line}").unwrap();
}
}
else {
log::debug!("Docs are up to date")
}
}
}
impl From<Configuration> for RuntimeConfiguration {
fn from(config: Configuration) -> Self {
let crate_dir = config.get_canonical_crate_dir();
let (crate_dir, manifest) =
match check_for_manifest(vec![&crate_dir, crate_dir.parent().unwrap()]) {
None => panic!(
"Could not find Cargo.toml in {} or its parent directory",
crate_dir.to_str().unwrap()
),
Some(m) => m,
};
let executables = manifest.executable_files(&crate_dir);
let lib = manifest.lib_file(&crate_dir);
let src_dir = crate_dir.join("src");
let src_dir = if src_dir.is_dir() && config.strip_src {
Some(src_dir)
}
else {
None
};
let doc_dir = config.doc_dir.join(&config.crate_name);
create_dir_all(&doc_dir).unwrap();
RuntimeConfiguration {
crate_dir,
crate_name: config.crate_name,
src_dir,
doc_dir: doc_dir.canonicalize().unwrap(),
force: config.force,
format: config.format,
max_visibility: config.visibility,
executables,
lib,
}
}
}
pub fn traverse_crate(config: Configuration) {
let runtime: RuntimeConfiguration = config.into();
log::debug!(
"Extracting docs for crate {} from {}",
&runtime.crate_name,
runtime.crate_dir.to_str().unwrap()
);
log::debug!(
"Generated docs will be stored in {}",
runtime.doc_dir.to_str().unwrap()
);
if let Some(file) = &runtime.lib {
let mut lib = CrateDirective::new(&runtime, file);
lib.filter_items(&runtime.max_visibility);
let mut modules = lib.file_directives.modules.clone();
while let Some(module) = modules.pop() {
for submodule in &module.file_directives.modules {
modules.push(submodule.clone());
}
runtime.write_doc_file(&module.source_code_file.path.clone(), module);
}
runtime.write_doc_file(&file.path, lib);
}
for file in &runtime.executables {
let mut exe = ExecutableDirective::new(&runtime, file);
exe.filter_items(&runtime.max_visibility);
let mut modules = exe.0.file_directives.modules.clone();
while let Some(module) = modules.pop() {
for submodule in &module.file_directives.modules {
modules.push(submodule.clone());
}
runtime.write_doc_file(&module.source_code_file.path.clone(), module);
}
runtime.write_doc_file(&file.path, exe);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_self() {
traverse_crate(Configuration {
crate_name: String::from("sphinx-rustdocgen"),
crate_dir: Path::new(".").to_owned(),
doc_dir: Path::new("../docs/crates").to_owned(),
format: Format::Rst,
visibility: DirectiveVisibility::Pvt,
force: true,
strip_src: true,
})
}
#[test]
fn test_markdown() {
traverse_crate(Configuration {
crate_name: String::from("test_crate"),
crate_dir: Path::new("../tests/test_crate").to_owned(),
doc_dir: Path::new("../tests/test_crate/docs/crates").to_owned(),
format: Format::Md,
visibility: DirectiveVisibility::Pvt,
force: true,
strip_src: true,
})
}
}