mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 03:42:17 +00:00
Generate docs basde on package root .roc file
This commit is contained in:
parent
e549456668
commit
15590fb31b
6 changed files with 115 additions and 188 deletions
|
@ -270,12 +270,13 @@ pub fn build_app<'a>() -> Command<'a> {
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
Command::new(CMD_DOCS)
|
Command::new(CMD_DOCS)
|
||||||
.about("Generate documentation for Roc modules (Work In Progress)")
|
.about("Generate documentation for a Roc package")
|
||||||
.arg(Arg::new(DIRECTORY_OR_FILES)
|
.arg(Arg::new(ROC_FILE)
|
||||||
.multiple_values(true)
|
.multiple_values(true)
|
||||||
.required(false)
|
.help("The package's main .roc file")
|
||||||
.help("The directory or files to build documentation for")
|
|
||||||
.allow_invalid_utf8(true)
|
.allow_invalid_utf8(true)
|
||||||
|
.required(false)
|
||||||
|
.default_value(DEFAULT_ROC_FILENAME),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.subcommand(Command::new(CMD_GLUE)
|
.subcommand(Command::new(CMD_GLUE)
|
||||||
|
|
|
@ -209,37 +209,9 @@ fn main() -> io::Result<()> {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
Some((CMD_DOCS, matches)) => {
|
Some((CMD_DOCS, matches)) => {
|
||||||
let maybe_values = matches.values_of_os(DIRECTORY_OR_FILES);
|
let root_filename = matches.value_of_os(ROC_FILE).unwrap();
|
||||||
|
|
||||||
let mut values: Vec<OsString> = Vec::new();
|
generate_docs_html(PathBuf::from(root_filename));
|
||||||
|
|
||||||
match maybe_values {
|
|
||||||
None => {
|
|
||||||
let mut os_string_values: Vec<OsString> = Vec::new();
|
|
||||||
read_all_roc_files(
|
|
||||||
&std::env::current_dir()?.as_os_str().to_os_string(),
|
|
||||||
&mut os_string_values,
|
|
||||||
)?;
|
|
||||||
for os_string in os_string_values {
|
|
||||||
values.push(os_string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(os_values) => {
|
|
||||||
for os_str in os_values {
|
|
||||||
values.push(os_str.to_os_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut roc_files = Vec::new();
|
|
||||||
|
|
||||||
// Populate roc_files
|
|
||||||
for os_str in values {
|
|
||||||
let metadata = fs::metadata(os_str.clone())?;
|
|
||||||
roc_files_recursive(os_str.as_os_str(), metadata.file_type(), &mut roc_files)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_docs_html(roc_files);
|
|
||||||
|
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
use crate::docs::DocEntry::DetachedDoc;
|
use crate::docs::DocEntry::DetachedDoc;
|
||||||
use crate::docs::TypeAnnotation::{Apply, BoundVariable, Function, NoTypeAnn, Record, TagUnion};
|
use crate::docs::TypeAnnotation::{Apply, BoundVariable, Function, NoTypeAnn, Record, TagUnion};
|
||||||
use crate::file::LoadedModule;
|
|
||||||
use roc_can::scope::Scope;
|
use roc_can::scope::Scope;
|
||||||
|
use roc_collections::MutMap;
|
||||||
use roc_module::ident::ModuleName;
|
use roc_module::ident::ModuleName;
|
||||||
use roc_module::symbol::IdentIds;
|
use roc_module::symbol::{IdentIds, ModuleId};
|
||||||
use roc_parse::ast::AssignedField;
|
use roc_parse::ast::AssignedField;
|
||||||
use roc_parse::ast::{self, ExtractSpaces, TypeHeader};
|
use roc_parse::ast::{self, ExtractSpaces, TypeHeader};
|
||||||
use roc_parse::ast::{CommentOrNewline, TypeDef, ValueDef};
|
use roc_parse::ast::{CommentOrNewline, TypeDef, ValueDef};
|
||||||
|
|
||||||
// Documentation generation requirements
|
// Documentation generation requirements
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Documentation {
|
|
||||||
pub name: String,
|
|
||||||
pub version: String,
|
|
||||||
pub docs: String,
|
|
||||||
pub modules: Vec<LoadedModule>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ModuleDocumentation {
|
pub struct ModuleDocumentation {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -100,8 +92,9 @@ pub fn generate_module_docs(
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
module_name: ModuleName,
|
module_name: ModuleName,
|
||||||
parsed_defs: &roc_parse::ast::Defs,
|
parsed_defs: &roc_parse::ast::Defs,
|
||||||
|
exposed_module_ids: &[ModuleId],
|
||||||
) -> ModuleDocumentation {
|
) -> ModuleDocumentation {
|
||||||
let entries = generate_entry_docs(&scope.locals.ident_ids, parsed_defs);
|
let entries = generate_entry_docs(&scope.locals.ident_ids, parsed_defs, exposed_module_ids);
|
||||||
|
|
||||||
ModuleDocumentation {
|
ModuleDocumentation {
|
||||||
name: module_name.as_str().to_string(),
|
name: module_name.as_str().to_string(),
|
||||||
|
@ -140,6 +133,7 @@ fn detached_docs_from_comments_and_new_lines<'a>(
|
||||||
fn generate_entry_docs<'a>(
|
fn generate_entry_docs<'a>(
|
||||||
ident_ids: &'a IdentIds,
|
ident_ids: &'a IdentIds,
|
||||||
defs: &roc_parse::ast::Defs<'a>,
|
defs: &roc_parse::ast::Defs<'a>,
|
||||||
|
exposed_module_ids: &[ModuleId],
|
||||||
) -> Vec<DocEntry> {
|
) -> Vec<DocEntry> {
|
||||||
use roc_parse::ast::Pattern;
|
use roc_parse::ast::Pattern;
|
||||||
|
|
||||||
|
@ -165,7 +159,7 @@ fn generate_entry_docs<'a>(
|
||||||
Err(value_index) => match &defs.value_defs[value_index.index()] {
|
Err(value_index) => match &defs.value_defs[value_index.index()] {
|
||||||
ValueDef::Annotation(loc_pattern, loc_ann) => {
|
ValueDef::Annotation(loc_pattern, loc_ann) => {
|
||||||
if let Pattern::Identifier(identifier) = loc_pattern.value {
|
if let Pattern::Identifier(identifier) = loc_pattern.value {
|
||||||
// Check if the definition is exposed
|
// Check if this module exposes the def
|
||||||
if ident_ids.get_id(identifier).is_some() {
|
if ident_ids.get_id(identifier).is_some() {
|
||||||
let name = identifier.to_string();
|
let name = identifier.to_string();
|
||||||
let doc_def = DocDef {
|
let doc_def = DocDef {
|
||||||
|
|
|
@ -376,6 +376,12 @@ fn start_phase<'a>(
|
||||||
state.cached_types.lock().contains_key(&module_id)
|
state.cached_types.lock().contains_key(&module_id)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let exposed_module_ids = state
|
||||||
|
.platform_data
|
||||||
|
.as_ref()
|
||||||
|
.map(|data| data.exposed_modules)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
BuildTask::CanonicalizeAndConstrain {
|
BuildTask::CanonicalizeAndConstrain {
|
||||||
parsed,
|
parsed,
|
||||||
dep_idents,
|
dep_idents,
|
||||||
|
@ -384,6 +390,7 @@ fn start_phase<'a>(
|
||||||
aliases,
|
aliases,
|
||||||
abilities_store,
|
abilities_store,
|
||||||
skip_constraint_gen,
|
skip_constraint_gen,
|
||||||
|
exposed_module_ids,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1159,6 +1166,7 @@ enum BuildTask<'a> {
|
||||||
exposed_symbols: VecSet<Symbol>,
|
exposed_symbols: VecSet<Symbol>,
|
||||||
aliases: MutMap<Symbol, Alias>,
|
aliases: MutMap<Symbol, Alias>,
|
||||||
abilities_store: PendingAbilitiesStore,
|
abilities_store: PendingAbilitiesStore,
|
||||||
|
exposed_module_ids: &'a [ModuleId],
|
||||||
skip_constraint_gen: bool,
|
skip_constraint_gen: bool,
|
||||||
},
|
},
|
||||||
Solve {
|
Solve {
|
||||||
|
@ -4371,7 +4379,7 @@ fn build_header<'a>(
|
||||||
};
|
};
|
||||||
home = module_ids.get_or_insert(&name);
|
home = module_ids.get_or_insert(&name);
|
||||||
|
|
||||||
// Ensure this module has an entry in the exposed_ident_ids map.
|
// Ensure this module has an entry in the ident_ids_by_module map.
|
||||||
ident_ids_by_module.get_or_insert(home);
|
ident_ids_by_module.get_or_insert(home);
|
||||||
|
|
||||||
// For each of our imports, add an entry to deps_by_name
|
// For each of our imports, add an entry to deps_by_name
|
||||||
|
@ -5209,6 +5217,7 @@ fn canonicalize_and_constrain<'a>(
|
||||||
imported_abilities_state: PendingAbilitiesStore,
|
imported_abilities_state: PendingAbilitiesStore,
|
||||||
parsed: ParsedModule<'a>,
|
parsed: ParsedModule<'a>,
|
||||||
skip_constraint_gen: bool,
|
skip_constraint_gen: bool,
|
||||||
|
exposed_module_ids: &[ModuleId],
|
||||||
) -> CanAndCon {
|
) -> CanAndCon {
|
||||||
let canonicalize_start = Instant::now();
|
let canonicalize_start = Instant::now();
|
||||||
|
|
||||||
|
@ -5273,17 +5282,24 @@ fn canonicalize_and_constrain<'a>(
|
||||||
}
|
}
|
||||||
HeaderType::Interface { name, .. }
|
HeaderType::Interface { name, .. }
|
||||||
| HeaderType::Builtin { name, .. }
|
| HeaderType::Builtin { name, .. }
|
||||||
| HeaderType::Hosted { name, .. } => {
|
| HeaderType::Hosted { name, .. }
|
||||||
|
if exposed_module_ids.contains(&parsed.module_id) =>
|
||||||
|
{
|
||||||
let mut scope = module_output.scope.clone();
|
let mut scope = module_output.scope.clone();
|
||||||
scope.add_docs_imports();
|
scope.add_docs_imports();
|
||||||
let docs = crate::docs::generate_module_docs(
|
let docs = crate::docs::generate_module_docs(
|
||||||
scope,
|
scope,
|
||||||
name.as_str().into(),
|
name.as_str().into(),
|
||||||
&parsed_defs_for_docs,
|
&parsed_defs_for_docs,
|
||||||
|
exposed_module_ids,
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(docs)
|
Some(docs)
|
||||||
}
|
}
|
||||||
|
HeaderType::Interface { .. } | HeaderType::Builtin { .. } | HeaderType::Hosted { .. } => {
|
||||||
|
// This module isn't exposed by the platform, so don't generate docs for it!
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// _before has an underscore because it's unused in --release builds
|
// _before has an underscore because it's unused in --release builds
|
||||||
|
@ -6126,6 +6142,7 @@ fn run_task<'a>(
|
||||||
aliases,
|
aliases,
|
||||||
abilities_store,
|
abilities_store,
|
||||||
skip_constraint_gen,
|
skip_constraint_gen,
|
||||||
|
exposed_module_ids,
|
||||||
} => {
|
} => {
|
||||||
let can_and_con = canonicalize_and_constrain(
|
let can_and_con = canonicalize_and_constrain(
|
||||||
arena,
|
arena,
|
||||||
|
@ -6136,6 +6153,7 @@ fn run_task<'a>(
|
||||||
abilities_store,
|
abilities_store,
|
||||||
parsed,
|
parsed,
|
||||||
skip_constraint_gen,
|
skip_constraint_gen,
|
||||||
|
exposed_module_ids,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(Msg::CanonicalizedAndConstrained(can_and_con))
|
Ok(Msg::CanonicalizedAndConstrained(can_and_con))
|
||||||
|
|
|
@ -10,7 +10,7 @@ use roc_code_markup::markup::nodes::MarkupNode;
|
||||||
use roc_code_markup::slow_pool::SlowPool;
|
use roc_code_markup::slow_pool::SlowPool;
|
||||||
use roc_highlight::highlight_parser::{highlight_defs, highlight_expr};
|
use roc_highlight::highlight_parser::{highlight_defs, highlight_expr};
|
||||||
use roc_load::docs::{DocEntry, TypeAnnotation};
|
use roc_load::docs::{DocEntry, TypeAnnotation};
|
||||||
use roc_load::docs::{Documentation, ModuleDocumentation, RecordField};
|
use roc_load::docs::{ModuleDocumentation, RecordField};
|
||||||
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
|
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
|
||||||
use roc_module::symbol::{IdentIdsByModule, Interns, ModuleId};
|
use roc_module::symbol::{IdentIdsByModule, Interns, ModuleId};
|
||||||
use roc_packaging::cache::{self, RocCacheDir};
|
use roc_packaging::cache::{self, RocCacheDir};
|
||||||
|
@ -25,21 +25,20 @@ mod html;
|
||||||
|
|
||||||
const BUILD_DIR: &str = "./generated-docs";
|
const BUILD_DIR: &str = "./generated-docs";
|
||||||
|
|
||||||
pub fn generate_docs_html(filenames: Vec<PathBuf>) {
|
pub fn generate_docs_html(root_file: PathBuf) {
|
||||||
let build_dir = Path::new(BUILD_DIR);
|
let build_dir = Path::new(BUILD_DIR);
|
||||||
let loaded_modules = load_modules_for_files(filenames);
|
let loaded_module = load_module_for_docs(root_file);
|
||||||
|
|
||||||
// TODO: get info from a package module; this is all hardcoded for now.
|
// TODO get these from the platform's source file rather than hardcoding them!
|
||||||
let package = Documentation {
|
let package_name = "Documentation".to_string();
|
||||||
name: "documentation".to_string(),
|
let version = String::new();
|
||||||
version: "".to_string(),
|
|
||||||
docs: "Package introduction or README.".to_string(),
|
|
||||||
modules: loaded_modules,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !build_dir.exists() {
|
// Clear out the generated-docs dir (we'll create a fresh one at the end)
|
||||||
fs::create_dir_all(build_dir).expect("TODO gracefully handle unable to create build dir");
|
if build_dir.exists() {
|
||||||
|
fs::remove_dir_all(build_dir)
|
||||||
|
.expect("TODO gracefully handle being unable to delete build dir");
|
||||||
}
|
}
|
||||||
|
fs::create_dir_all(build_dir).expect("TODO gracefully handle being unable to create build dir");
|
||||||
|
|
||||||
// Copy over the assets
|
// Copy over the assets
|
||||||
fs::write(
|
fs::write(
|
||||||
|
@ -60,29 +59,18 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
|
||||||
)
|
)
|
||||||
.expect("TODO gracefully handle failing to make the favicon");
|
.expect("TODO gracefully handle failing to make the favicon");
|
||||||
|
|
||||||
let module_pairs = package.modules.iter().flat_map(|loaded_module| {
|
let module_pairs = loaded_module
|
||||||
loaded_module
|
.documentation
|
||||||
.documentation
|
.iter()
|
||||||
.iter()
|
.flat_map(|(module_id, module)| {
|
||||||
.filter_map(move |(module_id, module)| {
|
let exposed_values = loaded_module
|
||||||
// TODO it seems this `documentation` dictionary has entries for
|
.exposed_values
|
||||||
// every module, but only the current module has any info in it.
|
.iter()
|
||||||
// We disregard the others, but probably this shouldn't bother
|
.map(|symbol| symbol.as_str(&loaded_module.interns).to_string())
|
||||||
// being a hash map in the first place if only one of its entries
|
.collect::<Vec<String>>();
|
||||||
// actually has interesting information in it?
|
|
||||||
if *module_id == loaded_module.module_id {
|
|
||||||
let exposed_values = loaded_module
|
|
||||||
.exposed_values
|
|
||||||
.iter()
|
|
||||||
.map(|symbol| symbol.as_str(&loaded_module.interns).to_string())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
Some((module, exposed_values))
|
Some((module, exposed_values))
|
||||||
} else {
|
});
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let template_html = include_str!("./static/index.html")
|
let template_html = include_str!("./static/index.html")
|
||||||
.replace("<!-- search.js -->", "/search.js")
|
.replace("<!-- search.js -->", "/search.js")
|
||||||
|
@ -93,7 +81,7 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
|
||||||
&module_pairs
|
&module_pairs
|
||||||
.clone()
|
.clone()
|
||||||
.map(|(module, _)| {
|
.map(|(module, _)| {
|
||||||
let href = sidebar_link_url(module);
|
let href = sidebar_link_url(module.name.as_str());
|
||||||
|
|
||||||
format!(r#"<link rel="prefetch" href="{href}"/>"#)
|
format!(r#"<link rel="prefetch" href="{href}"/>"#)
|
||||||
})
|
})
|
||||||
|
@ -106,48 +94,40 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Write each package's module docs html file
|
// Write each package's module docs html file
|
||||||
for loaded_module in package.modules.iter() {
|
for (module_id, module_docs) in loaded_module.documentation.iter() {
|
||||||
for (module_id, module_docs) in loaded_module.documentation.iter() {
|
let module_name = module_docs.name.as_str();
|
||||||
if *module_id == loaded_module.module_id {
|
let module_dir = build_dir.join(module_name.replace('.', "/").as_str());
|
||||||
let module_dir = build_dir.join(module_docs.name.replace('.', "/").as_str());
|
|
||||||
|
|
||||||
fs::create_dir_all(&module_dir)
|
fs::create_dir_all(&module_dir)
|
||||||
.expect("TODO gracefully handle not being able to create the module dir");
|
.expect("TODO gracefully handle not being able to create the module dir");
|
||||||
|
|
||||||
let rendered_module = template_html
|
let rendered_module = template_html
|
||||||
.replace(
|
.replace(
|
||||||
"<!-- Page title -->",
|
"<!-- Page title -->",
|
||||||
page_title(&package, module_docs).as_str(),
|
page_title(package_name.as_str(), module_name).as_str(),
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
"<!-- Package Name and Version -->",
|
"<!-- Package Name and Version -->",
|
||||||
render_name_and_version(package.name.as_str(), package.version.as_str())
|
render_name_and_version(package_name.as_str(), version.as_str()).as_str(),
|
||||||
.as_str(),
|
)
|
||||||
)
|
.replace(
|
||||||
.replace(
|
"<!-- Module Docs -->",
|
||||||
"<!-- Module Docs -->",
|
render_module_documentation(module_docs, &loaded_module).as_str(),
|
||||||
render_module_documentation(module_docs, loaded_module).as_str(),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
fs::write(module_dir.join("index.html"), rendered_module).expect(
|
fs::write(module_dir.join("index.html"), rendered_module)
|
||||||
"TODO gracefully handle failing to write index.html inside module's dir",
|
.expect("TODO gracefully handle failing to write index.html inside module's dir");
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("🎉 Docs generated in {}", build_dir.display());
|
println!("🎉 Docs generated in {}", build_dir.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sidebar_link_url(module: &ModuleDocumentation) -> String {
|
fn sidebar_link_url(module_name: &str) -> String {
|
||||||
format!("{}{}", base_url(), module.name.as_str())
|
format!("{}{}", base_url(), module_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn page_title(package: &Documentation, module: &ModuleDocumentation) -> String {
|
fn page_title(package_name: &str, module_name: &str) -> String {
|
||||||
let package_name = &package.name;
|
format!("<title>{module_name} - {package_name}</title>")
|
||||||
let module_name = &module.name;
|
|
||||||
let title = format!("<title>{module_name} - {package_name}</title>");
|
|
||||||
title
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// converts plain-text code to highlighted html
|
// converts plain-text code to highlighted html
|
||||||
|
@ -222,12 +202,8 @@ fn render_module_documentation(
|
||||||
if should_render_entry {
|
if should_render_entry {
|
||||||
buf.push_str("<section>");
|
buf.push_str("<section>");
|
||||||
|
|
||||||
let mut href = String::new();
|
|
||||||
href.push('#');
|
|
||||||
href.push_str(doc_def.name.as_str());
|
|
||||||
|
|
||||||
let name = doc_def.name.as_str();
|
let name = doc_def.name.as_str();
|
||||||
|
let href = format!("#{name}");
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
|
|
||||||
content.push_str(
|
content.push_str(
|
||||||
|
@ -241,15 +217,11 @@ fn render_module_documentation(
|
||||||
|
|
||||||
let type_ann = &doc_def.type_annotation;
|
let type_ann = &doc_def.type_annotation;
|
||||||
|
|
||||||
match type_ann {
|
if !matches!(type_ann, TypeAnnotation::NoTypeAnn) {
|
||||||
TypeAnnotation::NoTypeAnn => {}
|
content.push_str(" : ");
|
||||||
_ => {
|
type_annotation_to_html(0, &mut content, type_ann, false);
|
||||||
content.push_str(" : ");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type_annotation_to_html(0, &mut content, type_ann, false);
|
|
||||||
|
|
||||||
buf.push_str(
|
buf.push_str(
|
||||||
html_to_string(
|
html_to_string(
|
||||||
"h3",
|
"h3",
|
||||||
|
@ -388,7 +360,7 @@ fn render_sidebar<'a, I: Iterator<Item = (&'a ModuleDocumentation, Vec<String>)>
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
|
||||||
for (module, exposed_values) in modules {
|
for (module, exposed_values) in modules {
|
||||||
let href = sidebar_link_url(module);
|
let href = sidebar_link_url(module.name.as_str());
|
||||||
let mut sidebar_entry_content = String::new();
|
let mut sidebar_entry_content = String::new();
|
||||||
|
|
||||||
sidebar_entry_content.push_str(
|
sidebar_entry_content.push_str(
|
||||||
|
@ -449,35 +421,29 @@ fn render_sidebar<'a, I: Iterator<Item = (&'a ModuleDocumentation, Vec<String>)>
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_modules_for_files(filenames: Vec<PathBuf>) -> Vec<LoadedModule> {
|
pub fn load_module_for_docs(filename: PathBuf) -> LoadedModule {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let mut modules = Vec::with_capacity(filenames.len());
|
let load_config = LoadConfig {
|
||||||
|
target_info: roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter
|
||||||
for filename in filenames {
|
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||||
let load_config = LoadConfig {
|
palette: roc_reporting::report::DEFAULT_PALETTE,
|
||||||
target_info: roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter
|
threading: Threading::AllAvailable,
|
||||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
exec_mode: ExecutionMode::Check,
|
||||||
palette: roc_reporting::report::DEFAULT_PALETTE,
|
};
|
||||||
threading: Threading::AllAvailable,
|
match roc_load::load_and_typecheck(
|
||||||
exec_mode: ExecutionMode::Check,
|
&arena,
|
||||||
};
|
filename,
|
||||||
match roc_load::load_and_typecheck(
|
Default::default(),
|
||||||
&arena,
|
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
||||||
filename,
|
load_config,
|
||||||
Default::default(),
|
) {
|
||||||
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
Ok(loaded) => loaded,
|
||||||
load_config,
|
Err(LoadingProblem::FormattedReport(report)) => {
|
||||||
) {
|
eprintln!("{}", report);
|
||||||
Ok(loaded) => modules.push(loaded),
|
std::process::exit(1);
|
||||||
Err(LoadingProblem::FormattedReport(report)) => {
|
|
||||||
eprintln!("{}", report);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
Err(e) => panic!("{:?}", e),
|
|
||||||
}
|
}
|
||||||
|
Err(e) => panic!("{:?}", e),
|
||||||
}
|
}
|
||||||
|
|
||||||
modules
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const INDENT: &str = " ";
|
const INDENT: &str = " ";
|
||||||
|
|
|
@ -1,51 +1,27 @@
|
||||||
//! Provides a binary that is only used for static build servers.
|
//! Provides a binary that is only used for static build servers.
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use roc_docs::generate_docs_html;
|
use roc_docs::generate_docs_html;
|
||||||
use std::fs::{self, FileType};
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
|
pub const ROC_FILE: &str = "ROC_FILE";
|
||||||
|
const DEFAULT_ROC_FILENAME: &str = "main.roc";
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
let matches = Command::new("roc-docs")
|
let matches = Command::new("roc-docs")
|
||||||
.about("Build HTML documentation files from the given .roc files")
|
.about("Generate documentation for a Roc package")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(DIRECTORY_OR_FILES)
|
Arg::new(ROC_FILE)
|
||||||
.multiple_values(true)
|
.multiple_values(true)
|
||||||
.required(true)
|
.help("The package's main .roc file")
|
||||||
.help("The directory or files to build documentation for")
|
.allow_invalid_utf8(true)
|
||||||
.allow_invalid_utf8(true),
|
.required(false)
|
||||||
|
.default_value(DEFAULT_ROC_FILENAME),
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let mut roc_files = Vec::new();
|
|
||||||
|
|
||||||
// Populate roc_files
|
// Populate roc_files
|
||||||
for os_str in matches.values_of_os(DIRECTORY_OR_FILES).unwrap() {
|
generate_docs_html(PathBuf::from(matches.value_of_os(ROC_FILE).unwrap()));
|
||||||
let metadata = fs::metadata(os_str)?;
|
|
||||||
roc_files_recursive(os_str, metadata.file_type(), &mut roc_files)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_docs_html(roc_files);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn roc_files_recursive<P: AsRef<Path>>(
|
|
||||||
path: P,
|
|
||||||
file_type: FileType,
|
|
||||||
roc_files: &mut Vec<PathBuf>,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
if file_type.is_dir() {
|
|
||||||
for entry_res in fs::read_dir(path)? {
|
|
||||||
let entry = entry_res?;
|
|
||||||
|
|
||||||
roc_files_recursive(entry.path(), entry.file_type()?, roc_files)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
roc_files.push(path.as_ref().to_path_buf());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue