Generate full wiki from typedefs file

This commit is contained in:
Filip Tibell 2023-02-22 22:10:20 +01:00
parent 32bca9dde5
commit ec040e420e
No known key found for this signature in database
4 changed files with 154 additions and 7 deletions

1
Cargo.lock generated
View file

@ -808,6 +808,7 @@ dependencies = [
"clap", "clap",
"console", "console",
"full_moon", "full_moon",
"futures-util",
"lazy_static", "lazy_static",
"lune", "lune",
"regex", "regex",

View file

@ -331,6 +331,7 @@ declare process: {
The third argument, `options`, can be passed as a dictionary of options to give to the child process. The third argument, `options`, can be passed as a dictionary of options to give to the child process.
The available options inside of the `options` dictionary are: The available options inside of the `options` dictionary are:
* `cwd` - The current working directory for the process * `cwd` - The current working directory for the process
* `env` - Extra environment variables to give to the process * `env` - Extra environment variables to give to the process
* `shell` - Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell * `shell` - Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell

View file

@ -26,6 +26,7 @@ tokio.workspace = true
reqwest.workspace = true reqwest.workspace = true
anyhow = "1.0.68" anyhow = "1.0.68"
futures-util = "0.3.26"
regex = "1.7.1" regex = "1.7.1"
serde_yaml = "0.9.17" serde_yaml = "0.9.17"

View file

@ -1,22 +1,166 @@
use std::{fmt::Write, path::PathBuf}; use std::{collections::HashMap, fmt::Write, path::PathBuf};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use futures_util::future::try_join_all;
use tokio::fs::{create_dir_all, write}; use tokio::fs::{create_dir_all, write};
use super::definitions::DefinitionsTree; use super::definitions::{DefinitionsItem, DefinitionsItemKind, DefinitionsTree};
pub const GENERATED_COMMENT_TAG: &str = "@generated with lune-cli"; const GENERATED_COMMENT_TAG: &str = "<!-- @generated with lune-cli -->";
const CATEGORY_NONE: &str = "uncategorized";
pub async fn generate_from_type_definitions(contents: &str) -> Result<()> { pub async fn generate_from_type_definitions(contents: &str) -> Result<()> {
let tree = DefinitionsTree::from_type_definitions(contents)?; let tree = DefinitionsTree::from_type_definitions(contents)?;
let mut dirs_to_write = Vec::new();
let mut files_to_write = Vec::new();
// Create the wiki dir at the repo root // Create the wiki dir at the repo root
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) let path_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../") .join("../../")
.canonicalize() .canonicalize()
.unwrap(); .unwrap();
create_dir_all(&root.join("wiki")) let path_wiki_dir = path_root.join("wiki");
.await dirs_to_write.push(path_wiki_dir.clone());
.context("Failed to create wiki dir")?; // Sort doc items into subcategories based on globals
let mut api_reference: HashMap<&str, Vec<DefinitionsItem>> = HashMap::new();
for top_level_item in tree
.children()
.iter()
.filter(|top_level| top_level.is_exported())
{
match top_level_item.kind() {
DefinitionsItemKind::Table => {
let category_name = top_level_item
.get_name()
.context("Missing name for top-level doc item")?;
let category = match api_reference.contains_key(category_name) {
true => api_reference.get_mut(category_name).unwrap(),
false => {
api_reference.insert(category_name, vec![]);
api_reference.get_mut(category_name).unwrap()
}
};
category.push(top_level_item.clone());
}
DefinitionsItemKind::Function => {
let category = match api_reference.contains_key(CATEGORY_NONE) {
true => api_reference.get_mut(CATEGORY_NONE).unwrap(),
false => {
api_reference.insert(CATEGORY_NONE, vec![]);
api_reference.get_mut(CATEGORY_NONE).unwrap()
}
};
category.push(top_level_item.clone());
}
_ => unimplemented!("Globals other than tables and functions are not yet implemented"),
}
}
// Generate our api reference folder
let path_api_ref = path_wiki_dir.join("api-reference");
dirs_to_write.push(path_api_ref.clone());
// Generate files for all subcategories
for (category_name, category_items) in api_reference {
if category_items.len() == 1 {
let item = category_items.first().unwrap();
let path = path_api_ref.join(category_name).with_extension("md");
let mut contents = String::new();
write!(contents, "{GENERATED_COMMENT_TAG}\n\n")?;
generate_markdown_documentation(&mut contents, item)?;
files_to_write.push((path, post_process_docs(contents)));
} else {
let path_subcategory = path_api_ref.join(category_name);
dirs_to_write.push(path_subcategory.clone());
for item in category_items {
let item_name = item
.get_name()
.context("Missing name for subcategory doc item")?;
let path = path_subcategory.join(item_name).with_extension("md");
let mut contents = String::new();
write!(contents, "{GENERATED_COMMENT_TAG}\n\n")?;
generate_markdown_documentation(&mut contents, &item)?;
files_to_write.push((path, post_process_docs(contents)));
}
}
}
// Write all dirs and files only when we know generation was successful
let futs_dirs = dirs_to_write
.drain(..)
.map(create_dir_all)
.collect::<Vec<_>>();
let futs_files = files_to_write
.drain(..)
.map(|(path, contents)| write(path, contents))
.collect::<Vec<_>>();
try_join_all(futs_dirs).await?;
try_join_all(futs_files).await?;
Ok(()) Ok(())
} }
fn generate_markdown_documentation(contents: &mut String, item: &DefinitionsItem) -> Result<()> {
match item.kind() {
DefinitionsItemKind::Table => {
write!(
contents,
"\n# {}\n",
item.get_name().context("Table is missing a name")?
)?;
}
DefinitionsItemKind::Property => {
write!(
contents,
"\n### `{}`\n",
item.get_name().context("Property is missing a name")?
)?;
}
DefinitionsItemKind::Function => {
write!(
contents,
"\n### `{}`\n",
item.get_name().context("Function is missing a name")?
)?;
}
DefinitionsItemKind::Description => {
write!(
contents,
"\n{}\n",
item.get_value().context("Description is missing a value")?
)?;
}
_ => {}
}
let descriptions = item
.children()
.iter()
.filter(|child| child.is_description())
.collect::<Vec<_>>();
let properties = item
.children()
.iter()
.filter(|child| child.is_property())
.collect::<Vec<_>>();
let functions = item
.children()
.iter()
.filter(|child| child.is_function())
.collect::<Vec<_>>();
for description in descriptions {
generate_markdown_documentation(contents, description)?;
}
if !properties.is_empty() {
write!(contents, "\n\n---\n\n## Properties\n\n")?;
}
for property in properties {
generate_markdown_documentation(contents, property)?;
}
if !functions.is_empty() {
write!(contents, "\n\n---\n\n## Functions\n\n")?;
}
for function in functions {
generate_markdown_documentation(contents, function)?;
}
Ok(())
}
fn post_process_docs(contents: String) -> String {
contents.replace("\n\n\n", "\n\n")
}