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:
Nick Cameron
2025-05-06 11:02:55 +12:00
committed by GitHub
parent cf2e9d4b91
commit 574d6dae7f
163 changed files with 1053 additions and 1414 deletions

View File

@ -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
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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}}

View 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}}