Add modules to docs (#6699)
* Support modules in docs Signed-off-by: Nick Cameron <nrc@ncameron.org> * shuffle around directories Signed-off-by: Nick Cameron <nrc@ncameron.org> --------- Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -8,10 +8,9 @@ use itertools::Itertools;
|
||||
use serde_json::json;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use super::kcl_doc::{ConstData, DocData, ExampleProperties, FnData, TyData};
|
||||
use super::kcl_doc::{ConstData, DocData, ExampleProperties, FnData, ModData, TyData};
|
||||
use crate::{docs::StdLibFn, std::StdLib, ExecutorContext};
|
||||
|
||||
const LANG_TOPICS: [&str; 4] = ["Types", "Modules", "Settings", "Known Issues"];
|
||||
// These types are declared in (KCL) std.
|
||||
const DECLARED_TYPES: [&str; 15] = [
|
||||
"any", "number", "string", "tag", "bool", "Sketch", "Solid", "Plane", "Helix", "Face", "Edge", "Point2d",
|
||||
@ -101,12 +100,13 @@ fn init_handlebars() -> Result<handlebars::Handlebars<'static>> {
|
||||
hbs.register_template_string("index", include_str!("templates/index.hbs"))?;
|
||||
hbs.register_template_string("function", include_str!("templates/function.hbs"))?;
|
||||
hbs.register_template_string("const", include_str!("templates/const.hbs"))?;
|
||||
hbs.register_template_string("module", include_str!("templates/module.hbs"))?;
|
||||
hbs.register_template_string("kclType", include_str!("templates/kclType.hbs"))?;
|
||||
|
||||
Ok(hbs)
|
||||
}
|
||||
|
||||
fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[DocData]) -> Result<()> {
|
||||
fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &ModData) -> Result<()> {
|
||||
let hbs = init_handlebars()?;
|
||||
|
||||
let mut functions = HashMap::new();
|
||||
@ -139,7 +139,7 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
|
||||
.push((name.to_owned(), format!("types#{name}")));
|
||||
}
|
||||
|
||||
for d in kcl_lib {
|
||||
for d in kcl_lib.all_docs() {
|
||||
if d.hide() {
|
||||
continue;
|
||||
}
|
||||
@ -148,6 +148,7 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
|
||||
DocData::Fn(_) => functions.entry(d.mod_name()).or_default(),
|
||||
DocData::Ty(_) => types.entry(d.mod_name()).or_default(),
|
||||
DocData::Const(_) => constants.entry(d.mod_name()).or_default(),
|
||||
DocData::Mod(_) => continue,
|
||||
};
|
||||
|
||||
group.push((d.preferred_name().to_owned(), d.file_name()));
|
||||
@ -159,6 +160,7 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
|
||||
fns.sort();
|
||||
let val = json!({
|
||||
"name": m,
|
||||
"file_name": m.replace("::", "-"),
|
||||
"items": fns.into_iter().map(|(n, f)| json!({
|
||||
"name": n,
|
||||
"file_name": f,
|
||||
@ -176,6 +178,7 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
|
||||
consts.sort();
|
||||
let val = json!({
|
||||
"name": m,
|
||||
"file_name": m.replace("::", "-"),
|
||||
"items": consts.into_iter().map(|(n, f)| json!({
|
||||
"name": n,
|
||||
"file_name": f,
|
||||
@ -193,6 +196,7 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
|
||||
tys.sort();
|
||||
let val = json!({
|
||||
"name": m,
|
||||
"file_name": m.replace("::", "-"),
|
||||
"items": tys.into_iter().map(|(n, f)| json!({
|
||||
"name": n,
|
||||
"file_name": f,
|
||||
@ -204,18 +208,7 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
|
||||
sorted_types.sort_by(|t1, t2| t1.0.cmp(&t2.0));
|
||||
let types_data: Vec<_> = sorted_types.into_iter().map(|(_, val)| val).collect();
|
||||
|
||||
let topics: Vec<_> = LANG_TOPICS
|
||||
.iter()
|
||||
.map(|name| {
|
||||
json!({
|
||||
"name": name,
|
||||
"file_name": name.to_lowercase().replace(' ', "-"),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let data = json!({
|
||||
"lang_topics": topics,
|
||||
"functions": functions_data,
|
||||
"consts": consts_data,
|
||||
"types": types_data,
|
||||
@ -223,7 +216,7 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
|
||||
|
||||
let output = hbs.render("index", &data)?;
|
||||
|
||||
expectorate::assert_contents("../../docs/kcl/index.md", &output);
|
||||
expectorate::assert_contents("../../docs/kcl-std/index.md", &output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -280,7 +273,48 @@ fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String)
|
||||
|
||||
let output = hbs.render("kclType", &data)?;
|
||||
let output = cleanup_types(&output);
|
||||
expectorate::assert_contents(format!("../../docs/kcl/{}.md", file_name), &output);
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", file_name), &output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_mod_from_kcl(m: &ModData, file_name: String) -> Result<()> {
|
||||
fn list_items(m: &ModData, namespace: &str) -> Vec<gltf_json::Value> {
|
||||
let mut items: Vec<_> = m
|
||||
.children
|
||||
.iter()
|
||||
.filter(|(k, _)| k.starts_with(namespace))
|
||||
.map(|(_, v)| (v.preferred_name(), v.file_name()))
|
||||
.collect();
|
||||
items.sort();
|
||||
items
|
||||
.into_iter()
|
||||
.map(|(n, f)| {
|
||||
json!({
|
||||
"name": n,
|
||||
"file_name": f,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
let hbs = init_handlebars()?;
|
||||
|
||||
// TODO for prelude, items from Rust
|
||||
let functions = list_items(m, "I:");
|
||||
let modules = list_items(m, "M:");
|
||||
let types = list_items(m, "T:");
|
||||
|
||||
let data = json!({
|
||||
"name": m.qual_name,
|
||||
"summary": m.summary,
|
||||
"description": m.description,
|
||||
"modules": modules,
|
||||
"functions": functions,
|
||||
"types": types,
|
||||
});
|
||||
|
||||
let output = hbs.render("module", &data)?;
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", file_name), &output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -326,7 +360,7 @@ fn generate_function_from_kcl(function: &FnData, file_name: String, example_name
|
||||
|
||||
let output = hbs.render("function", &data)?;
|
||||
let output = &cleanup_types(&output);
|
||||
expectorate::assert_contents(format!("../../docs/kcl/{}.md", file_name), output);
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", file_name), output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -355,7 +389,7 @@ fn generate_const_from_kcl(cnst: &ConstData, file_name: String, example_name: St
|
||||
});
|
||||
|
||||
let output = hbs.render("const", &data)?;
|
||||
expectorate::assert_contents(format!("../../docs/kcl/{}.md", file_name), &output);
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", file_name), &output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -425,7 +459,7 @@ fn generate_function(internal_fn: Box<dyn StdLibFn>) -> Result<()> {
|
||||
// Fix the links to the types.
|
||||
output = cleanup_types(&output);
|
||||
|
||||
expectorate::assert_contents(format!("../../docs/kcl/{}.md", fn_name), &output);
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", fn_name), &output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -577,9 +611,9 @@ fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String {
|
||||
// we might want to restore the links by not checking `fmt_for_text` here.
|
||||
|
||||
if fmt_for_text && SPECIAL_TYPES.contains(&ty) {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl/types#{ty})")
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-lang/types#{ty})")
|
||||
} else if fmt_for_text && DECLARED_TYPES.contains(&ty) {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl/types/std-types-{ty})")
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-{ty})")
|
||||
} else {
|
||||
format!("{prefix}{ty}{suffix}")
|
||||
}
|
||||
@ -624,26 +658,22 @@ fn test_generate_stdlib_markdown_docs() {
|
||||
generate_function(internal_fn.clone()).unwrap();
|
||||
}
|
||||
|
||||
for d in &kcl_std {
|
||||
for d in kcl_std.all_docs() {
|
||||
match d {
|
||||
DocData::Fn(f) => generate_function_from_kcl(f, d.file_name(), d.example_name()).unwrap(),
|
||||
DocData::Const(c) => generate_const_from_kcl(c, d.file_name(), d.example_name()).unwrap(),
|
||||
DocData::Ty(t) => generate_type_from_kcl(t, d.file_name(), d.example_name()).unwrap(),
|
||||
DocData::Mod(m) => generate_mod_from_kcl(m, d.file_name()).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
// Copy manually written docs to the output directory.
|
||||
for entry in fs::read_dir("../../docs/kcl-src").unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
fs::copy(&path, Path::new("../../docs/kcl").join(path.file_name().unwrap())).unwrap();
|
||||
}
|
||||
generate_mod_from_kcl(&kcl_std, "modules/std".to_owned()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_stdlib_json_schema() {
|
||||
// If this test fails and you've modified the AST or something else which affects the json repr
|
||||
// of stdlib functions, you should rerun the test with `EXPECTORATE=overwrite` to create new
|
||||
// test data, then check `/docs/kcl/std.json` to ensure the changes are expected.
|
||||
// test data, then check `/docs/kcl-std/std.json` to ensure the changes are expected.
|
||||
// Alternatively, run `just redo-kcl-stdlib-docs` (make sure to have just installed).
|
||||
let stdlib = StdLib::new();
|
||||
let combined = stdlib.combined();
|
||||
@ -657,7 +687,7 @@ fn test_generate_stdlib_json_schema() {
|
||||
})
|
||||
.collect();
|
||||
expectorate::assert_contents(
|
||||
"../../docs/kcl/std.json",
|
||||
"../../docs/kcl-std/std.json",
|
||||
&serde_json::to_string_pretty(&json_data).unwrap(),
|
||||
);
|
||||
}
|
||||
@ -665,16 +695,16 @@ fn test_generate_stdlib_json_schema() {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_code_in_topics() {
|
||||
let mut join_set = JoinSet::new();
|
||||
for name in LANG_TOPICS {
|
||||
let filename = format!("../../docs/kcl/{}.md", name.to_lowercase().replace(' ', "-"));
|
||||
let text = std::fs::read_to_string(&filename).unwrap();
|
||||
for entry in fs::read_dir("../../docs/kcl-lang").unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
let text = std::fs::read_to_string(&path).unwrap();
|
||||
|
||||
for (i, (eg, attr)) in find_examples(&text, &filename).into_iter().enumerate() {
|
||||
if attr == "norun" {
|
||||
for (i, (eg, attr)) in find_examples(&text, &path).into_iter().enumerate() {
|
||||
if attr.contains("norun") || !attr.contains("kcl") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let f = filename.clone();
|
||||
let f = path.display().to_string();
|
||||
join_set.spawn(async move { (format!("{f}, example {i}"), run_example(&eg).await) });
|
||||
}
|
||||
}
|
||||
@ -687,7 +717,7 @@ async fn test_code_in_topics() {
|
||||
assert!(results.is_empty(), "Failures: {}", results.join(", "))
|
||||
}
|
||||
|
||||
fn find_examples(text: &str, filename: &str) -> Vec<(String, String)> {
|
||||
fn find_examples(text: &str, filename: &Path) -> Vec<(String, String)> {
|
||||
let mut buf = String::new();
|
||||
let mut attr = String::new();
|
||||
let mut in_eg = false;
|
||||
@ -711,7 +741,7 @@ fn find_examples(text: &str, filename: &str) -> Vec<(String, String)> {
|
||||
}
|
||||
}
|
||||
|
||||
assert!(!in_eg, "Unclosed code tags in {}", filename);
|
||||
assert!(!in_eg, "Unclosed code tags in {}", filename.display());
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -9,23 +9,14 @@ use tower_lsp::lsp_types::{
|
||||
use crate::{
|
||||
execution::annotations,
|
||||
parsing::{
|
||||
ast::types::{Annotation, ImportSelector, ItemVisibility, Node, VariableKind},
|
||||
ast::types::{Annotation, ImportSelector, ItemVisibility, Node, NonCodeValue, VariableKind},
|
||||
token::NumericSuffix,
|
||||
},
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
pub fn walk_prelude() -> Vec<DocData> {
|
||||
let mut visitor = CollectionVisitor::default();
|
||||
visitor.visit_module("prelude", "", WalkForNames::All).unwrap();
|
||||
visitor.result.into_values().collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct CollectionVisitor {
|
||||
name: String,
|
||||
result: HashMap<String, DocData>,
|
||||
id: usize,
|
||||
pub fn walk_prelude() -> ModData {
|
||||
visit_module("prelude", "", WalkForNames::All).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -50,93 +41,122 @@ impl<'a> WalkForNames<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectionVisitor {
|
||||
fn visit_module(&mut self, name: &str, preferred_prefix: &str, names: WalkForNames) -> Result<(), String> {
|
||||
let old_name = std::mem::replace(&mut self.name, name.to_owned());
|
||||
let source = crate::modules::read_std(name).unwrap();
|
||||
let parsed = crate::parsing::parse_str(source, ModuleId::from_usize(self.id))
|
||||
.parse_errs_as_err()
|
||||
.unwrap();
|
||||
self.id += 1;
|
||||
fn visit_module(name: &str, preferred_prefix: &str, names: WalkForNames) -> Result<ModData, String> {
|
||||
let mut result = ModData::new(name, preferred_prefix);
|
||||
|
||||
for n in &parsed.body {
|
||||
if n.visibility() != ItemVisibility::Export {
|
||||
continue;
|
||||
let source = crate::modules::read_std(name).unwrap();
|
||||
let parsed = crate::parsing::parse_str(source, ModuleId::from_usize(0))
|
||||
.parse_errs_as_err()
|
||||
.unwrap();
|
||||
|
||||
// TODO handle examples; use with_comments
|
||||
let mut summary = String::new();
|
||||
let mut description = None;
|
||||
for n in &parsed.non_code_meta.start_nodes {
|
||||
match &n.value {
|
||||
NonCodeValue::BlockComment { value, .. } if value.starts_with('/') => {
|
||||
let line = value[1..].trim();
|
||||
if line.is_empty() {
|
||||
match &mut description {
|
||||
None => description = Some(String::new()),
|
||||
Some(d) => d.push_str("\n\n"),
|
||||
}
|
||||
} else {
|
||||
match &mut description {
|
||||
None => {
|
||||
summary.push_str(line);
|
||||
summary.push(' ');
|
||||
}
|
||||
Some(d) => {
|
||||
d.push_str(line);
|
||||
d.push(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match n {
|
||||
crate::parsing::ast::types::BodyItem::ImportStatement(import) => match &import.path {
|
||||
crate::parsing::ast::types::ImportPath::Std { path } => match &import.selector {
|
||||
ImportSelector::Glob(..) => self.visit_module(&path[1], "", names.clone())?,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
if !summary.is_empty() {
|
||||
result.summary = Some(summary);
|
||||
}
|
||||
result.description = description;
|
||||
|
||||
for n in &parsed.body {
|
||||
if n.visibility() != ItemVisibility::Export {
|
||||
continue;
|
||||
}
|
||||
match n {
|
||||
crate::parsing::ast::types::BodyItem::ImportStatement(import) => match &import.path {
|
||||
crate::parsing::ast::types::ImportPath::Std { path } => {
|
||||
let m = match &import.selector {
|
||||
ImportSelector::Glob(..) => Some(visit_module(&path[1], "", names.clone())?),
|
||||
ImportSelector::None { .. } => {
|
||||
let name = import.module_name().unwrap();
|
||||
if names.contains(&name) {
|
||||
self.visit_module(&path[1], &format!("{}::", name), WalkForNames::All)?;
|
||||
Some(visit_module(&path[1], &format!("{}::", name), WalkForNames::All)?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ImportSelector::List { items } => {
|
||||
self.visit_module(&path[1], "", names.intersect(items.iter().map(|n| &*n.name.name)))?
|
||||
}
|
||||
},
|
||||
p => return Err(format!("Unexpected import: `{p}`")),
|
||||
},
|
||||
crate::parsing::ast::types::BodyItem::VariableDeclaration(var) => {
|
||||
if !names.contains(var.name()) {
|
||||
continue;
|
||||
}
|
||||
let qual_name = if self.name == "prelude" {
|
||||
"std::".to_owned()
|
||||
} else {
|
||||
format!("std::{}::", self.name)
|
||||
ImportSelector::List { items } => Some(visit_module(
|
||||
&path[1],
|
||||
"",
|
||||
names.intersect(items.iter().map(|n| &*n.name.name)),
|
||||
)?),
|
||||
};
|
||||
let mut dd = match var.kind {
|
||||
VariableKind::Fn => DocData::Fn(FnData::from_ast(var, qual_name, preferred_prefix, name)),
|
||||
VariableKind::Const => {
|
||||
DocData::Const(ConstData::from_ast(var, qual_name, preferred_prefix, name))
|
||||
}
|
||||
};
|
||||
let key = format!("I:{}", dd.qual_name());
|
||||
if self.result.contains_key(&key) {
|
||||
continue;
|
||||
if let Some(m) = m {
|
||||
result.children.insert(format!("M:{}", m.qual_name), DocData::Mod(m));
|
||||
}
|
||||
|
||||
dd.with_meta(&var.outer_attrs);
|
||||
for a in &var.outer_attrs {
|
||||
dd.with_comments(&a.pre_comments);
|
||||
}
|
||||
dd.with_comments(n.get_comments());
|
||||
|
||||
self.result.insert(key, dd);
|
||||
}
|
||||
crate::parsing::ast::types::BodyItem::TypeDeclaration(ty) => {
|
||||
if !names.contains(ty.name()) {
|
||||
continue;
|
||||
}
|
||||
let qual_name = if self.name == "prelude" {
|
||||
"std::".to_owned()
|
||||
} else {
|
||||
format!("std::{}::", self.name)
|
||||
};
|
||||
let mut dd = DocData::Ty(TyData::from_ast(ty, qual_name, preferred_prefix, name));
|
||||
let key = format!("T:{}", dd.qual_name());
|
||||
if self.result.contains_key(&key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dd.with_meta(&ty.outer_attrs);
|
||||
for a in &ty.outer_attrs {
|
||||
dd.with_comments(&a.pre_comments);
|
||||
}
|
||||
dd.with_comments(n.get_comments());
|
||||
|
||||
self.result.insert(key, dd);
|
||||
p => return Err(format!("Unexpected import: `{p}`")),
|
||||
},
|
||||
crate::parsing::ast::types::BodyItem::VariableDeclaration(var) => {
|
||||
if !names.contains(var.name()) {
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
let qual = format!("{}::", &result.qual_name);
|
||||
let mut dd = match var.kind {
|
||||
VariableKind::Fn => DocData::Fn(FnData::from_ast(var, qual, preferred_prefix, name)),
|
||||
VariableKind::Const => DocData::Const(ConstData::from_ast(var, qual, preferred_prefix, name)),
|
||||
};
|
||||
let key = format!("I:{}", dd.qual_name());
|
||||
if result.children.contains_key(&key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dd.with_meta(&var.outer_attrs);
|
||||
for a in &var.outer_attrs {
|
||||
dd.with_comments(&a.pre_comments);
|
||||
}
|
||||
dd.with_comments(n.get_comments());
|
||||
|
||||
result.children.insert(key, dd);
|
||||
}
|
||||
}
|
||||
crate::parsing::ast::types::BodyItem::TypeDeclaration(ty) => {
|
||||
if !names.contains(ty.name()) {
|
||||
continue;
|
||||
}
|
||||
let qual = format!("{}::", &result.qual_name);
|
||||
let mut dd = DocData::Ty(TyData::from_ast(ty, qual, preferred_prefix, name));
|
||||
let key = format!("T:{}", dd.qual_name());
|
||||
if result.children.contains_key(&key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.name = old_name;
|
||||
Ok(())
|
||||
dd.with_meta(&ty.outer_attrs);
|
||||
for a in &ty.outer_attrs {
|
||||
dd.with_comments(&a.pre_comments);
|
||||
}
|
||||
dd.with_comments(n.get_comments());
|
||||
|
||||
result.children.insert(key, dd);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -144,6 +164,7 @@ pub enum DocData {
|
||||
Fn(FnData),
|
||||
Const(ConstData),
|
||||
Ty(TyData),
|
||||
Mod(ModData),
|
||||
}
|
||||
|
||||
impl DocData {
|
||||
@ -152,6 +173,7 @@ impl DocData {
|
||||
DocData::Fn(f) => &f.name,
|
||||
DocData::Const(c) => &c.name,
|
||||
DocData::Ty(t) => &t.name,
|
||||
DocData::Mod(m) => &m.name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,6 +183,7 @@ impl DocData {
|
||||
DocData::Fn(f) => &f.preferred_name,
|
||||
DocData::Const(c) => &c.preferred_name,
|
||||
DocData::Ty(t) => &t.preferred_name,
|
||||
DocData::Mod(m) => &m.preferred_name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,6 +192,7 @@ impl DocData {
|
||||
DocData::Fn(f) => &f.qual_name,
|
||||
DocData::Const(c) => &c.qual_name,
|
||||
DocData::Ty(t) => &t.qual_name,
|
||||
DocData::Mod(m) => &m.qual_name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,6 +203,7 @@ impl DocData {
|
||||
DocData::Fn(f) => &f.module_name,
|
||||
DocData::Const(c) => &c.module_name,
|
||||
DocData::Ty(t) => &t.module_name,
|
||||
DocData::Mod(m) => &m.module_name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +213,7 @@ impl DocData {
|
||||
DocData::Fn(f) => format!("functions/{}", f.qual_name.replace("::", "-")),
|
||||
DocData::Const(c) => format!("consts/{}", c.qual_name.replace("::", "-")),
|
||||
DocData::Ty(t) => format!("types/{}", t.qual_name.replace("::", "-")),
|
||||
DocData::Mod(m) => format!("modules/{}", m.qual_name.replace("::", "-")),
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,6 +223,7 @@ impl DocData {
|
||||
DocData::Fn(f) => format!("fn_{}", f.qual_name.replace("::", "-")),
|
||||
DocData::Const(c) => format!("const_{}", c.qual_name.replace("::", "-")),
|
||||
DocData::Ty(t) => format!("ty_{}", t.qual_name.replace("::", "-")),
|
||||
DocData::Mod(_) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,6 +239,7 @@ impl DocData {
|
||||
}
|
||||
&t.qual_name
|
||||
}
|
||||
DocData::Mod(m) => &m.qual_name,
|
||||
};
|
||||
q[0..q.rfind("::").unwrap()].to_owned()
|
||||
}
|
||||
@ -222,14 +250,16 @@ impl DocData {
|
||||
DocData::Fn(f) => f.properties.doc_hidden || f.properties.deprecated,
|
||||
DocData::Const(c) => c.properties.doc_hidden || c.properties.deprecated,
|
||||
DocData::Ty(t) => t.properties.doc_hidden || t.properties.deprecated,
|
||||
DocData::Mod(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_completion_item(&self) -> CompletionItem {
|
||||
pub fn to_completion_item(&self) -> Option<CompletionItem> {
|
||||
match self {
|
||||
DocData::Fn(f) => f.to_completion_item(),
|
||||
DocData::Const(c) => c.to_completion_item(),
|
||||
DocData::Ty(t) => t.to_completion_item(),
|
||||
DocData::Fn(f) => Some(f.to_completion_item()),
|
||||
DocData::Const(c) => Some(c.to_completion_item()),
|
||||
DocData::Ty(t) => Some(t.to_completion_item()),
|
||||
DocData::Mod(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,6 +268,7 @@ impl DocData {
|
||||
DocData::Fn(f) => Some(f.to_signature_help()),
|
||||
DocData::Const(_) => None,
|
||||
DocData::Ty(_) => None,
|
||||
DocData::Mod(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,6 +277,7 @@ impl DocData {
|
||||
DocData::Fn(f) => f.with_meta(attrs),
|
||||
DocData::Const(c) => c.with_meta(attrs),
|
||||
DocData::Ty(t) => t.with_meta(attrs),
|
||||
DocData::Mod(m) => m.with_meta(attrs),
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,6 +286,7 @@ impl DocData {
|
||||
DocData::Fn(f) => f.with_comments(comments),
|
||||
DocData::Const(c) => c.with_comments(comments),
|
||||
DocData::Ty(t) => t.with_comments(comments),
|
||||
DocData::Mod(m) => m.with_comments(comments),
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,9 +296,17 @@ impl DocData {
|
||||
DocData::Fn(f) => f.examples.iter(),
|
||||
DocData::Const(c) => c.examples.iter(),
|
||||
DocData::Ty(t) => t.examples.iter(),
|
||||
DocData::Mod(_) => unimplemented!(),
|
||||
}
|
||||
.filter_map(|(s, p)| (!p.norun).then_some(s))
|
||||
}
|
||||
|
||||
fn expect_mod(&self) -> &ModData {
|
||||
match self {
|
||||
DocData::Mod(m) => m,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -384,6 +425,70 @@ impl ConstData {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModData {
|
||||
pub name: String,
|
||||
/// How the module is indexed, etc.
|
||||
pub preferred_name: String,
|
||||
/// The fully qualified name.
|
||||
pub qual_name: String,
|
||||
/// The summary of the module.
|
||||
pub summary: Option<String>,
|
||||
/// The description of the module.
|
||||
pub description: Option<String>,
|
||||
pub module_name: String,
|
||||
|
||||
pub children: HashMap<String, DocData>,
|
||||
}
|
||||
|
||||
impl ModData {
|
||||
fn new(name: &str, preferred_prefix: &str) -> Self {
|
||||
let (qual_name, module_name) = if name == "prelude" {
|
||||
("std".to_owned(), String::new())
|
||||
} else {
|
||||
(format!("std::{}", name), "std".to_owned())
|
||||
};
|
||||
Self {
|
||||
preferred_name: format!("{preferred_prefix}{name}"),
|
||||
name: name.to_owned(),
|
||||
qual_name,
|
||||
summary: None,
|
||||
description: None,
|
||||
children: HashMap::new(),
|
||||
module_name,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn find_by_name(&self, name: &str) -> Option<&DocData> {
|
||||
if let Some(result) = self.children.values().find(|dd| dd.name() == name) {
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
#[allow(clippy::iter_over_hash_type)]
|
||||
for (k, v) in &self.children {
|
||||
if k.starts_with("M:") {
|
||||
if let Some(result) = v.expect_mod().find_by_name(name) {
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn all_docs(&self) -> impl Iterator<Item = &DocData> {
|
||||
let result = self.children.values();
|
||||
// TODO really this should be recursive, currently assume std is only one module deep.
|
||||
result.chain(
|
||||
self.children
|
||||
.iter()
|
||||
.filter(|(k, _)| k.starts_with("M:"))
|
||||
.flat_map(|(_, d)| d.expect_mod().children.values()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FnData {
|
||||
/// The name of the function.
|
||||
@ -985,6 +1090,29 @@ impl ApplyMeta for FnData {
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplyMeta for ModData {
|
||||
fn apply_docs(
|
||||
&mut self,
|
||||
summary: Option<String>,
|
||||
description: Option<String>,
|
||||
examples: Vec<(String, ExampleProperties)>,
|
||||
) {
|
||||
self.summary = summary;
|
||||
self.description = description;
|
||||
assert!(examples.is_empty());
|
||||
}
|
||||
|
||||
fn deprecated(&mut self, deprecated: bool) {
|
||||
assert!(!deprecated);
|
||||
}
|
||||
|
||||
fn doc_hidden(&mut self, doc_hidden: bool) {
|
||||
assert!(!doc_hidden);
|
||||
}
|
||||
|
||||
fn impl_kind(&mut self, _: annotations::Impl) {}
|
||||
}
|
||||
|
||||
impl ApplyMeta for TyData {
|
||||
fn apply_docs(
|
||||
&mut self,
|
||||
@ -1050,16 +1178,14 @@ mod test {
|
||||
#[test]
|
||||
fn smoke() {
|
||||
let result = walk_prelude();
|
||||
for d in result {
|
||||
if let DocData::Const(d) = d {
|
||||
if d.name == "PI" {
|
||||
assert!(d.value.unwrap().starts_with('3'));
|
||||
assert_eq!(d.ty, Some("number(_?)".to_owned()));
|
||||
assert_eq!(d.qual_name, "std::math::PI");
|
||||
assert!(d.summary.is_some());
|
||||
assert!(!d.examples.is_empty());
|
||||
return;
|
||||
}
|
||||
if let DocData::Const(d) = result.find_by_name("PI").unwrap() {
|
||||
if d.name == "PI" {
|
||||
assert!(d.value.as_ref().unwrap().starts_with('3'));
|
||||
assert_eq!(d.ty, Some("number(_?)".to_owned()));
|
||||
assert_eq!(d.qual_name, "std::math::PI");
|
||||
assert!(d.summary.is_some());
|
||||
assert!(!d.examples.is_empty());
|
||||
return;
|
||||
}
|
||||
}
|
||||
panic!("didn't find PI");
|
||||
@ -1088,8 +1214,19 @@ mod test {
|
||||
async fn kcl_test_examples() {
|
||||
let std = walk_prelude();
|
||||
let mut errs = Vec::new();
|
||||
for d in std {
|
||||
if d.module_name() != STD_MOD_NAME {
|
||||
|
||||
let data = if STD_MOD_NAME == "prelude" {
|
||||
&std
|
||||
} else {
|
||||
std.children
|
||||
.get(&format!("M:std::{STD_MOD_NAME}"))
|
||||
.unwrap()
|
||||
.expect_mod()
|
||||
};
|
||||
|
||||
#[allow(clippy::iter_over_hash_type)]
|
||||
for d in data.children.values() {
|
||||
if let DocData::Mod(_) = d {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -918,7 +918,7 @@ mod tests {
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_fillet() {
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(fillet_fn) = data.into_iter().find(|d| d.name() == "fillet").unwrap() else {
|
||||
let DocData::Fn(fillet_fn) = data.find_by_name("fillet").unwrap() else {
|
||||
panic!();
|
||||
};
|
||||
let snippet = fillet_fn.to_autocomplete_snippet();
|
||||
@ -946,7 +946,7 @@ mod tests {
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_revolve() {
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(revolve_fn) = data.into_iter().find(|d| d.name() == "revolve").unwrap() else {
|
||||
let DocData::Fn(revolve_fn) = data.find_by_name("revolve").unwrap() else {
|
||||
panic!();
|
||||
};
|
||||
let snippet = revolve_fn.to_autocomplete_snippet();
|
||||
@ -956,7 +956,7 @@ mod tests {
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_circle() {
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(circle_fn) = data.into_iter().find(|d| d.name() == "circle").unwrap() else {
|
||||
let DocData::Fn(circle_fn) = data.find_by_name("circle").unwrap() else {
|
||||
panic!();
|
||||
};
|
||||
let snippet = circle_fn.to_autocomplete_snippet();
|
||||
@ -1027,7 +1027,7 @@ mod tests {
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_helix() {
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(helix_fn) = data.into_iter().find(|d| d.name() == "helix").unwrap() else {
|
||||
let DocData::Fn(helix_fn) = data.find_by_name("helix").unwrap() else {
|
||||
panic!();
|
||||
};
|
||||
let snippet = helix_fn.to_autocomplete_snippet();
|
||||
|
20
rust/kcl-lib/src/docs/templates/index.hbs
vendored
20
rust/kcl-lib/src/docs/templates/index.hbs
vendored
@ -6,37 +6,31 @@ layout: manual
|
||||
|
||||
## Table of Contents
|
||||
|
||||
### Language
|
||||
|
||||
{{#each lang_topics}}
|
||||
* [`{{name}}`](kcl/{{file_name}})
|
||||
{{/each}}
|
||||
|
||||
### Functions
|
||||
|
||||
{{#each functions}}
|
||||
* **{{name}}**
|
||||
* [**{{name}}**](/docs/kcl-std/modules/{{file_name}})
|
||||
{{#each items}}
|
||||
* [`{{name}}`](kcl/{{file_name}})
|
||||
* [`{{name}}`](/docs/kcl-std/{{file_name}})
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
### Constants
|
||||
|
||||
{{#each consts}}
|
||||
* **{{name}}**
|
||||
* [**{{name}}**](/docs/kcl-std/modules/{{file_name}})
|
||||
{{#each items}}
|
||||
* [`{{name}}`](kcl/{{file_name}})
|
||||
* [`{{name}}`](/docs/kcl-std/{{file_name}})
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
### Types
|
||||
|
||||
See also the [types overview](types)
|
||||
See also the [types overview](/docs/kcl-lang/types)
|
||||
|
||||
{{#each types}}
|
||||
* **{{name}}**
|
||||
* [**{{name}}**](/docs/kcl-std/modules/{{file_name}})
|
||||
{{#each items}}
|
||||
* [`{{name}}`](kcl/{{file_name}})
|
||||
* [`{{name}}`](/docs/kcl-std/{{file_name}})
|
||||
{{/each}}
|
||||
{{/each}}
|
33
rust/kcl-lib/src/docs/templates/module.hbs
vendored
Normal file
33
rust/kcl-lib/src/docs/templates/module.hbs
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: "{{name}}"
|
||||
excerpt: "{{safe_yaml summary}}"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
{{{summary}}}
|
||||
|
||||
{{{description}}}
|
||||
|
||||
{{#if modules}}
|
||||
## Modules
|
||||
|
||||
{{#each modules}}
|
||||
* [`{{name}}`](/docs/kcl-std/{{file_name}})
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{#if functions}}
|
||||
## Functions and constants
|
||||
|
||||
{{#each functions}}
|
||||
* [`{{name}}`](/docs/kcl-std/{{file_name}})
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{#if types}}
|
||||
## Types
|
||||
|
||||
{{#each types}}
|
||||
* [`{{name}}`](/docs/kcl-std/{{file_name}})
|
||||
{{/each}}
|
||||
{{/if}}
|
Reference in New Issue
Block a user