mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 04:50:36 +00:00
Added global types to documentation site
This commit is contained in:
parent
1559e69de6
commit
d4d9108947
9 changed files with 204 additions and 60 deletions
|
@ -8,6 +8,12 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## Added
|
||||
|
||||
- Added global types to documentation site
|
||||
|
||||
## `0.6.6` - April 30th, 2023
|
||||
|
||||
### Added
|
||||
|
|
|
@ -123,6 +123,9 @@ impl Cli {
|
|||
|| self.generate_docs_file
|
||||
|| self.generate_gitbook_dir;
|
||||
if generate_file_requested {
|
||||
if self.generate_gitbook_dir {
|
||||
generate_gitbook_dir_from_definitions(&TYPEDEFS_DIR).await?;
|
||||
}
|
||||
let definitions = generate_typedefs_file_from_dir(&TYPEDEFS_DIR);
|
||||
if self.generate_luau_types {
|
||||
generate_and_save_file(FILE_NAME_LUAU_TYPES, "Luau type definitions", || {
|
||||
|
@ -142,9 +145,6 @@ impl Cli {
|
|||
})
|
||||
.await?;
|
||||
}
|
||||
if self.generate_gitbook_dir {
|
||||
generate_gitbook_dir_from_definitions(&definitions).await?;
|
||||
}
|
||||
}
|
||||
if self.script_path.is_none() {
|
||||
// Only generating typedefs without running a script is completely
|
||||
|
|
|
@ -9,6 +9,7 @@ use super::{
|
|||
pub struct DefinitionsItemBuilder {
|
||||
exported: bool,
|
||||
kind: Option<DefinitionsItemKind>,
|
||||
typ: Option<String>,
|
||||
name: Option<String>,
|
||||
meta: Option<String>,
|
||||
value: Option<String>,
|
||||
|
@ -41,6 +42,11 @@ impl DefinitionsItemBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_type(mut self, typ: String) -> Self {
|
||||
self.typ = Some(typ);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_meta<S: AsRef<str>>(mut self, meta: S) -> Self {
|
||||
self.meta = Some(meta.as_ref().to_string());
|
||||
self
|
||||
|
@ -88,10 +94,11 @@ impl DefinitionsItemBuilder {
|
|||
pub fn build(self) -> Result<DefinitionsItem> {
|
||||
if let Some(kind) = self.kind {
|
||||
let mut children = self.children;
|
||||
children.sort();
|
||||
children.sort_by(|left, right| left.name.cmp(&right.name));
|
||||
Ok(DefinitionsItem {
|
||||
exported: self.exported,
|
||||
kind,
|
||||
typ: self.typ,
|
||||
name: self.name,
|
||||
meta: self.meta,
|
||||
value: self.value,
|
||||
|
@ -104,3 +111,19 @@ impl DefinitionsItemBuilder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&DefinitionsItem> for DefinitionsItemBuilder {
|
||||
fn from(value: &DefinitionsItem) -> Self {
|
||||
Self {
|
||||
exported: value.exported,
|
||||
kind: Some(value.kind),
|
||||
typ: value.typ.clone(),
|
||||
name: value.name.clone(),
|
||||
meta: value.meta.clone(),
|
||||
value: value.value.clone(),
|
||||
children: value.children.clone(),
|
||||
args: value.args.clone(),
|
||||
rets: value.rets.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,12 +45,13 @@ impl DefinitionsItemFunctionRet {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DefinitionsItem {
|
||||
#[serde(skip_serializing_if = "skip_serialize_is_false")]
|
||||
pub(super) exported: bool,
|
||||
pub(super) kind: DefinitionsItemKind,
|
||||
pub(super) typ: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(super) name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -95,12 +96,6 @@ impl PartialOrd for DefinitionsItem {
|
|||
}
|
||||
}
|
||||
|
||||
impl Ord for DefinitionsItem {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.partial_cmp(other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl DefinitionsItem {
|
||||
pub fn is_exported(&self) -> bool {
|
||||
|
@ -111,6 +106,10 @@ impl DefinitionsItem {
|
|||
self.kind.is_root()
|
||||
}
|
||||
|
||||
pub fn is_type(&self) -> bool {
|
||||
self.kind.is_type()
|
||||
}
|
||||
|
||||
pub fn is_table(&self) -> bool {
|
||||
self.kind.is_table()
|
||||
}
|
||||
|
@ -139,6 +138,10 @@ impl DefinitionsItem {
|
|||
self.name.as_deref()
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> Option<String> {
|
||||
self.typ.clone()
|
||||
}
|
||||
|
||||
pub fn get_meta(&self) -> Option<&str> {
|
||||
self.meta.as_deref()
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||
#[serde(rename_all = "PascalCase")]
|
||||
pub enum DefinitionsItemKind {
|
||||
Root,
|
||||
Type,
|
||||
Table,
|
||||
Property,
|
||||
Function,
|
||||
|
@ -19,6 +20,10 @@ impl DefinitionsItemKind {
|
|||
self == DefinitionsItemKind::Root
|
||||
}
|
||||
|
||||
pub fn is_type(self) -> bool {
|
||||
self == DefinitionsItemKind::Type
|
||||
}
|
||||
|
||||
pub fn is_table(self) -> bool {
|
||||
self == DefinitionsItemKind::Table
|
||||
}
|
||||
|
@ -47,6 +52,7 @@ impl fmt::Display for DefinitionsItemKind {
|
|||
"{}",
|
||||
match self {
|
||||
Self::Root => "Root",
|
||||
Self::Type => "Type",
|
||||
Self::Table => "Table",
|
||||
Self::Property => "Property",
|
||||
Self::Function => "Function",
|
||||
|
|
|
@ -12,7 +12,7 @@ use regex::Regex;
|
|||
|
||||
use super::{
|
||||
builder::DefinitionsItemBuilder, item::DefinitionsItem, moonwave::parse_moonwave_style_comment,
|
||||
type_info_ext::TypeInfoExt,
|
||||
type_info_ext::TypeInfoExt, DefinitionsItemKind,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -26,6 +26,7 @@ struct DefinitionsParserItem {
|
|||
pub struct DefinitionsParser {
|
||||
found_top_level_items: BTreeMap<String, DefinitionsParserItem>,
|
||||
found_top_level_types: HashMap<String, TypeInfo>,
|
||||
found_top_level_comments: HashMap<String, Option<String>>,
|
||||
found_top_level_declares: Vec<String>,
|
||||
}
|
||||
|
||||
|
@ -34,6 +35,7 @@ impl DefinitionsParser {
|
|||
Self {
|
||||
found_top_level_items: BTreeMap::new(),
|
||||
found_top_level_types: HashMap::new(),
|
||||
found_top_level_comments: HashMap::new(),
|
||||
found_top_level_declares: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +71,7 @@ impl DefinitionsParser {
|
|||
// Parse contents into top-level parser items for later use
|
||||
let mut found_top_level_items = BTreeMap::new();
|
||||
let mut found_top_level_types = HashMap::new();
|
||||
let mut found_top_level_comments = HashMap::new();
|
||||
let ast =
|
||||
full_moon::parse(&resulting_contents).context("Failed to parse type definitions")?;
|
||||
for stmt in ast.nodes().stmts() {
|
||||
|
@ -80,28 +83,36 @@ impl DefinitionsParser {
|
|||
_ => None,
|
||||
} {
|
||||
let name = declaration.type_name().token().to_string();
|
||||
let comment = find_token_moonwave_comment(token_reference);
|
||||
found_top_level_items.insert(
|
||||
name.clone(),
|
||||
DefinitionsParserItem {
|
||||
name: name.clone(),
|
||||
comment: find_token_moonwave_comment(token_reference),
|
||||
comment: comment.clone(),
|
||||
type_info: declaration.type_definition().clone(),
|
||||
},
|
||||
);
|
||||
found_top_level_types.insert(name, declaration.type_definition().clone());
|
||||
found_top_level_types.insert(name.clone(), declaration.type_definition().clone());
|
||||
found_top_level_comments.insert(name, comment);
|
||||
}
|
||||
}
|
||||
// Store results
|
||||
self.found_top_level_items = found_top_level_items;
|
||||
self.found_top_level_types = found_top_level_types;
|
||||
self.found_top_level_comments = found_top_level_comments;
|
||||
self.found_top_level_declares = found_declares;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_parser_item_into_doc_item(&self, item: DefinitionsParserItem) -> DefinitionsItem {
|
||||
fn convert_parser_item_into_doc_item(
|
||||
&self,
|
||||
item: DefinitionsParserItem,
|
||||
kind: Option<DefinitionsItemKind>,
|
||||
) -> DefinitionsItem {
|
||||
let mut builder = DefinitionsItemBuilder::new()
|
||||
.with_kind(item.type_info.parse_definitions_kind())
|
||||
.with_name(&item.name);
|
||||
.with_kind(kind.unwrap_or_else(|| item.type_info.parse_definitions_kind()))
|
||||
.with_name(&item.name)
|
||||
.with_type(item.type_info.to_string());
|
||||
if self.found_top_level_declares.contains(&item.name) {
|
||||
builder = builder.as_exported();
|
||||
}
|
||||
|
@ -123,6 +134,7 @@ impl DefinitionsParser {
|
|||
comment: find_token_moonwave_comment(name),
|
||||
type_info: field.value().clone(),
|
||||
},
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -137,14 +149,16 @@ impl DefinitionsParser {
|
|||
*/
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub fn drain(&mut self) -> Result<Vec<DefinitionsItem>> {
|
||||
let mut results = Vec::new();
|
||||
let mut resulting_items = Vec::new();
|
||||
for top_level_item in self.found_top_level_items.values() {
|
||||
results.push(self.convert_parser_item_into_doc_item(top_level_item.clone()));
|
||||
resulting_items
|
||||
.push(self.convert_parser_item_into_doc_item(top_level_item.clone(), None));
|
||||
}
|
||||
self.found_top_level_items = BTreeMap::new();
|
||||
self.found_top_level_types = HashMap::new();
|
||||
self.found_top_level_comments = HashMap::new();
|
||||
self.found_top_level_declares = Vec::new();
|
||||
Ok(results)
|
||||
Ok(resulting_items)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use super::{
|
|||
parser::DefinitionsParser,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct DefinitionsTree(DefinitionsItem);
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -18,8 +18,8 @@ All globals that are not available under a specific scope.
|
|||
These are to be used directly without indexing a global table first.
|
||||
";
|
||||
|
||||
pub async fn generate_from_type_definitions(contents: &str) -> Result<()> {
|
||||
let tree = DefinitionsTree::from_type_definitions(contents)?;
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub async fn generate_from_type_definitions(contents: HashMap<String, String>) -> Result<()> {
|
||||
let mut dirs_to_write = Vec::new();
|
||||
let mut files_to_write = Vec::new();
|
||||
// Create the gitbook dir at the repo root
|
||||
|
@ -37,22 +37,42 @@ pub async fn generate_from_type_definitions(contents: &str) -> Result<()> {
|
|||
dirs_to_write.push(path_gitbook_api_dir.clone());
|
||||
// Sort doc items into subcategories based on globals
|
||||
let mut api_reference = HashMap::new();
|
||||
let mut no_category = Vec::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 =
|
||||
get_name(top_level_item).context("Missing name for top-level doc item")?;
|
||||
api_reference.insert(category_name, top_level_item.clone());
|
||||
let mut without_main_item = Vec::new();
|
||||
for (typedef_name, typedef_contents) in contents {
|
||||
let tree = DefinitionsTree::from_type_definitions(typedef_contents)?;
|
||||
let main = tree.children().iter().find(
|
||||
|c| matches!(c.get_name(), Some(s) if s.to_lowercase() == typedef_name.to_lowercase()),
|
||||
);
|
||||
if let Some(main) = main {
|
||||
let children = tree
|
||||
.children()
|
||||
.iter()
|
||||
.filter_map(|child| {
|
||||
if child == main {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
DefinitionsItemBuilder::from(child)
|
||||
.with_kind(DefinitionsItemKind::Type)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let root = DefinitionsItemBuilder::new()
|
||||
.with_kind(main.kind())
|
||||
.with_name(main.get_name().unwrap())
|
||||
.with_children(main.children())
|
||||
.with_children(&children);
|
||||
api_reference.insert(
|
||||
typedef_name.clone(),
|
||||
root.build().expect("Failed to build root definitions item"),
|
||||
);
|
||||
} else {
|
||||
for top_level_item in tree.children() {
|
||||
without_main_item.push(top_level_item.clone());
|
||||
}
|
||||
DefinitionsItemKind::Function => {
|
||||
no_category.push(top_level_item.clone());
|
||||
}
|
||||
_ => unimplemented!("Globals other than tables and functions are not yet implemented"),
|
||||
}
|
||||
}
|
||||
// Insert globals with no category into a new "Uncategorized" global
|
||||
|
@ -61,7 +81,7 @@ pub async fn generate_from_type_definitions(contents: &str) -> Result<()> {
|
|||
DefinitionsItemBuilder::new()
|
||||
.with_kind(DefinitionsItemKind::Table)
|
||||
.with_name("Uncategorized")
|
||||
.with_children(&no_category)
|
||||
.with_children(&without_main_item)
|
||||
.with_child(
|
||||
DefinitionsItemBuilder::new()
|
||||
.with_kind(DefinitionsItemKind::Description)
|
||||
|
@ -78,7 +98,7 @@ pub async fn generate_from_type_definitions(contents: &str) -> Result<()> {
|
|||
.with_extension("md");
|
||||
let mut contents = String::new();
|
||||
write!(contents, "{GENERATED_COMMENT_TAG}\n\n")?;
|
||||
generate_markdown_documentation(&mut contents, &category_item, 0)?;
|
||||
generate_markdown_documentation(&mut contents, &category_item, None, 0)?;
|
||||
files_to_write.push((path, post_process_docs(contents)));
|
||||
}
|
||||
// Write all dirs and files only when we know generation was successful
|
||||
|
@ -113,13 +133,16 @@ fn get_name(item: &DefinitionsItem) -> Result<String> {
|
|||
.context("Definitions item is missing a name")
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn generate_markdown_documentation(
|
||||
contents: &mut String,
|
||||
item: &DefinitionsItem,
|
||||
parent: Option<&DefinitionsItem>,
|
||||
depth: usize,
|
||||
) -> Result<()> {
|
||||
match item.kind() {
|
||||
DefinitionsItemKind::Table
|
||||
DefinitionsItemKind::Type
|
||||
| DefinitionsItemKind::Table
|
||||
| DefinitionsItemKind::Property
|
||||
| DefinitionsItemKind::Function => {
|
||||
write!(
|
||||
|
@ -146,17 +169,31 @@ fn generate_markdown_documentation(
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
if item.kind().is_function() && !item.args().is_empty() {
|
||||
if item.is_function() && !item.args().is_empty() {
|
||||
let args = item
|
||||
.args()
|
||||
.iter()
|
||||
.map(|arg| format!("{}: {}", arg.name, arg.typedef))
|
||||
.collect::<Vec<_>>();
|
||||
.map(|arg| format!("{}: {}", arg.name.trim(), arg.typedef.trim()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
.replace("_: T...", "T...");
|
||||
let func_name = item.get_name().unwrap_or("_");
|
||||
let parent_name = parent.unwrap().get_name().unwrap_or("_");
|
||||
let parent_pre = if parent_name.to_lowercase() == "uncategorized" {
|
||||
String::new()
|
||||
} else {
|
||||
format!("{parent_name}.")
|
||||
};
|
||||
write!(
|
||||
contents,
|
||||
"\n```lua\nfunction {}({})\n```\n",
|
||||
"\n```lua\nfunction {parent_pre}{func_name}({args})\n```\n",
|
||||
)?;
|
||||
} else if item.is_type() {
|
||||
write!(
|
||||
contents,
|
||||
"\n```lua\ntype {} = {}\n```\n",
|
||||
item.get_name().unwrap_or("_"),
|
||||
args.join(", ")
|
||||
item.get_type().unwrap_or_else(|| "{}".to_string()).trim()
|
||||
)?;
|
||||
}
|
||||
let descriptions = item
|
||||
|
@ -174,24 +211,50 @@ fn generate_markdown_documentation(
|
|||
.iter()
|
||||
.filter(|child| child.is_function())
|
||||
.collect::<Vec<_>>();
|
||||
let types = item
|
||||
.children()
|
||||
.iter()
|
||||
.filter(|child| child.is_type())
|
||||
.collect::<Vec<_>>();
|
||||
for description in descriptions {
|
||||
generate_markdown_documentation(contents, description, depth + 1)?;
|
||||
generate_markdown_documentation(contents, description, Some(item), depth + 1)?;
|
||||
}
|
||||
if !properties.is_empty() {
|
||||
write!(contents, "\n\n---\n\n## Properties\n\n")?;
|
||||
}
|
||||
for property in properties {
|
||||
generate_markdown_documentation(contents, property, depth + 1)?;
|
||||
}
|
||||
if !functions.is_empty() {
|
||||
write!(contents, "\n\n---\n\n## Functions\n\n")?;
|
||||
}
|
||||
for function in functions {
|
||||
generate_markdown_documentation(contents, function, depth + 1)?;
|
||||
if !item.is_type() {
|
||||
if !properties.is_empty() {
|
||||
write!(contents, "\n\n---\n\n## Properties\n\n")?;
|
||||
}
|
||||
for property in properties {
|
||||
generate_markdown_documentation(contents, property, Some(item), depth + 1)?;
|
||||
}
|
||||
if !functions.is_empty() {
|
||||
write!(contents, "\n\n---\n\n## Functions\n\n")?;
|
||||
}
|
||||
for function in functions {
|
||||
generate_markdown_documentation(contents, function, Some(item), depth + 1)?;
|
||||
}
|
||||
if !types.is_empty() {
|
||||
write!(contents, "\n\n---\n\n## Types\n\n")?;
|
||||
}
|
||||
for typ in types {
|
||||
generate_markdown_documentation(contents, typ, Some(item), depth + 1)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_process_docs(contents: String) -> String {
|
||||
contents.replace("\n\n\n", "\n\n")
|
||||
let no_empty_lines = contents
|
||||
.lines()
|
||||
.map(|line| {
|
||||
if line.chars().all(char::is_whitespace) {
|
||||
""
|
||||
} else {
|
||||
line
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
no_empty_lines
|
||||
.replace("\n\n\n", "\n\n")
|
||||
.replace("\n\n\n", "\n\n")
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use include_dir::Dir;
|
||||
use regex::Regex;
|
||||
|
||||
|
@ -9,10 +12,36 @@ mod selene_defs;
|
|||
pub mod definitions;
|
||||
|
||||
pub use docs_file::generate_from_type_definitions as generate_docs_json_from_definitions;
|
||||
pub use gitbook_dir::generate_from_type_definitions as generate_gitbook_dir_from_definitions;
|
||||
pub use luau_defs::generate_from_type_definitions as generate_luau_defs_from_definitions;
|
||||
pub use selene_defs::generate_from_type_definitions as generate_selene_defs_from_definitions;
|
||||
|
||||
pub async fn generate_gitbook_dir_from_definitions(dir: &Dir<'_>) -> Result<()> {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for entry in dir.find("*.luau").unwrap() {
|
||||
let entry_file = entry.as_file().unwrap();
|
||||
let entry_name = entry_file.path().file_name().unwrap().to_string_lossy();
|
||||
|
||||
let typedef_name = entry_name.trim_end_matches(".luau");
|
||||
let typedef_contents = entry_file
|
||||
.contents_utf8()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.replace(
|
||||
&format!("export type {typedef_name} = "),
|
||||
&format!("declare {}: ", typedef_name.to_ascii_lowercase()),
|
||||
)
|
||||
.replace("export type ", "type ");
|
||||
|
||||
result.insert(typedef_name.to_string(), typedef_contents);
|
||||
}
|
||||
|
||||
match gitbook_dir::generate_from_type_definitions(result).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_typedefs_file_from_dir(dir: &Dir<'_>) -> String {
|
||||
let mut result = String::new();
|
||||
|
||||
|
|
Loading…
Reference in a new issue