Test code examples in docs and add docs for per-file settings (#5474)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
		@ -6,9 +6,15 @@ layout: manual
 | 
			
		||||
 | 
			
		||||
## Table of Contents
 | 
			
		||||
 | 
			
		||||
* [Types](kcl/types)
 | 
			
		||||
* [Modules](kcl/modules)
 | 
			
		||||
* [Known Issues](kcl/KNOWN-ISSUES)
 | 
			
		||||
### Language
 | 
			
		||||
 | 
			
		||||
* [`Types`](kcl/types)
 | 
			
		||||
* [`Modules`](kcl/modules)
 | 
			
		||||
* [`Settings`](kcl/settings)
 | 
			
		||||
* [`Known Issues`](kcl/known-issues)
 | 
			
		||||
 | 
			
		||||
### Standard library
 | 
			
		||||
 | 
			
		||||
* **`std`**
 | 
			
		||||
  * [`HALF_TURN`](kcl/const_std-HALF_TURN)
 | 
			
		||||
  * [`QUARTER_TURN`](kcl/const_std-QUARTER_TURN)
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ export fn increment(x) {
 | 
			
		||||
Other files in the project can now import functions that have been exported.
 | 
			
		||||
This makes them available to use in another file.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
// main.kcl
 | 
			
		||||
import increment from "util.kcl"
 | 
			
		||||
 | 
			
		||||
@ -48,13 +48,13 @@ export fn decrement(x) {
 | 
			
		||||
 | 
			
		||||
When importing, you can import multiple functions at once.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
import increment, decrement from "util.kcl"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Imported symbols can be renamed for convenience or to avoid name collisions.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
import increment as inc, decrement as dec from "util.kcl"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@ -63,13 +63,13 @@ import increment as inc, decrement as dec from "util.kcl"
 | 
			
		||||
`import` can also be used to import files from other CAD systems. The format of the statement is the
 | 
			
		||||
same as for KCL files. You can only import the whole file, not items from it. E.g.,
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
import "tests/inputs/cube.obj"
 | 
			
		||||
 | 
			
		||||
// Use `cube` just like a KCL object.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
import "tests/inputs/cube-2.sldprt" as cube
 | 
			
		||||
 | 
			
		||||
// Use `cube` just like a KCL object.
 | 
			
		||||
@ -78,7 +78,7 @@ import "tests/inputs/cube-2.sldprt" as cube
 | 
			
		||||
You can make the file format explicit using a format attribute (useful if using a different
 | 
			
		||||
extension), e.g.,
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
@(format = obj)
 | 
			
		||||
import "tests/inputs/cube"
 | 
			
		||||
```
 | 
			
		||||
@ -87,7 +87,7 @@ For formats lacking unit data (such as STL, OBJ, or PLY files), the default
 | 
			
		||||
unit of measurement is millimeters. Alternatively you may specify the unit
 | 
			
		||||
by using an attirbute. Likewise, you can also specify a coordinate system. E.g.,
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
@(unitLength = ft, coords = opengl)
 | 
			
		||||
import "tests/inputs/cube.obj"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								docs/kcl/settings.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								docs/kcl/settings.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
---
 | 
			
		||||
title: "KCL settings"
 | 
			
		||||
excerpt: "Documentation of settings for the KCL language and Zoo Modeling App."
 | 
			
		||||
layout: manual
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Per-file settings
 | 
			
		||||
 | 
			
		||||
Settings which affect a single file are configured using the settings attribute.
 | 
			
		||||
This must be at the top of the KCL file (comments before the attribute are permitted).
 | 
			
		||||
E.g.,
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
// The settings attribute.
 | 
			
		||||
@settings(defaultLengthUnit = in)
 | 
			
		||||
 | 
			
		||||
// The rest of your KCL code goes below...
 | 
			
		||||
 | 
			
		||||
x = 42 // Represents 42 inches.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The settings attribute may contain multiple properties separated by commas.
 | 
			
		||||
Valid properties are:
 | 
			
		||||
 | 
			
		||||
- `defaultLengthUnit`: the default length unit to use for numbers declared in this file.
 | 
			
		||||
  - Accepted values: `mm`, `cm`, `m`, `in` (inches), `ft` (feet), `yd` (yards).
 | 
			
		||||
- `defaultAngleUnit`: the default angle unit to use for numbers declared in this file.
 | 
			
		||||
  - Accepted values: `deg` (degrees), `rad` (radians).
 | 
			
		||||
 | 
			
		||||
These settings override any project-wide settings (configured in project.toml or via the UI).
 | 
			
		||||
@ -74,18 +74,15 @@ fn myFn(x) {
 | 
			
		||||
 | 
			
		||||
As you can see above `myFn` just returns whatever it is given.
 | 
			
		||||
 | 
			
		||||
KCL's early drafts used positional arguments, but we now use keyword arguments. If you declare a
 | 
			
		||||
function like this:
 | 
			
		||||
KCL's early drafts used positional arguments, but we now use keyword arguments:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
// If you declare a function like this
 | 
			
		||||
fn add(left, right) {
 | 
			
		||||
  return left + right
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You can call it like this:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
// You can call it like this:
 | 
			
		||||
total = add(left = 1, right = 2)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@ -111,14 +108,14 @@ three = add(1, delta = 2)
 | 
			
		||||
 | 
			
		||||
It can be hard to read repeated function calls, because of all the nested brackets.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
i = 1
 | 
			
		||||
x = h(g(f(i)))
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You can make this easier to read by breaking it into many declarations, but that is a bit annoying.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
i  = 1
 | 
			
		||||
x0 = f(i)
 | 
			
		||||
x1 = g(x0)
 | 
			
		||||
@ -133,12 +130,12 @@ the `%` in the right-hand side.
 | 
			
		||||
So, this means `x |> f(%) |> g(%)` is shorthand for `g(f(x))`. The code example above, with its
 | 
			
		||||
somewhat-clunky `x0` and `x1` constants could be rewritten as
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
i = 1
 | 
			
		||||
x = i
 | 
			
		||||
|> f(%)
 | 
			
		||||
|> g(%)
 | 
			
		||||
|> h(%)
 | 
			
		||||
  |> f(%)
 | 
			
		||||
  |> g(%)
 | 
			
		||||
  |> h(%)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This helps keep your code neat and avoid unnecessary declarations.
 | 
			
		||||
@ -147,12 +144,12 @@ This helps keep your code neat and avoid unnecessary declarations.
 | 
			
		||||
 | 
			
		||||
Say you have a long pipeline of sketch functions, like this:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
startSketch()
 | 
			
		||||
|> line(%, end = [3, 4])
 | 
			
		||||
|> line(%, end = [10, 10])
 | 
			
		||||
|> line(%, end = [-13, -14])
 | 
			
		||||
|> close(%)
 | 
			
		||||
```norun
 | 
			
		||||
startSketchOn('XZ')
 | 
			
		||||
  |> line(%, end = [3, 4])
 | 
			
		||||
  |> line(%, end = [10, 10])
 | 
			
		||||
  |> line(%, end = [-13, -14])
 | 
			
		||||
  |> close(%)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In this example, each function call outputs a sketch, and it gets put into the next function call via
 | 
			
		||||
@ -162,12 +159,12 @@ If a function call uses an unlabeled first parameter, it will default to `%` if
 | 
			
		||||
means that `|> line(%, end = [3, 4])` and `|> line(end = [3, 4])` are equivalent! So the above
 | 
			
		||||
could be rewritten as 
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
startSketch()
 | 
			
		||||
|> line(end = [3, 4])
 | 
			
		||||
|> line(end = [10, 10])
 | 
			
		||||
|> line(end = [-13, -14])
 | 
			
		||||
|> close()
 | 
			
		||||
```norun
 | 
			
		||||
startSketchOn('XZ')
 | 
			
		||||
 |> line(end = [3, 4])
 | 
			
		||||
 |> line(end = [10, 10])
 | 
			
		||||
 |> line(end = [-13, -14])
 | 
			
		||||
 |> close()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Note that we are still in the process of migrating KCL's standard library to use keyword arguments. So some
 | 
			
		||||
@ -184,7 +181,7 @@ Tags are used to give a name (tag) to a specific path.
 | 
			
		||||
The syntax for declaring a tag is `$myTag` you would use it in the following
 | 
			
		||||
way:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt(origin, %)
 | 
			
		||||
  |> angledLine({angle = 0, length = 191.26}, %, $rectangleSegmentA001)
 | 
			
		||||
@ -216,7 +213,7 @@ use the tag `rectangleSegmentA001` in any function or expression in the file.
 | 
			
		||||
 | 
			
		||||
However if the code was written like this:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
fn rect(origin) {
 | 
			
		||||
  return startSketchOn('XZ')
 | 
			
		||||
    |> startProfileAt(origin, %)
 | 
			
		||||
@ -244,7 +241,7 @@ However you likely want to use those tags somewhere outside the `rect` function.
 | 
			
		||||
Tags are accessible through the sketch group they are declared in.
 | 
			
		||||
For example the following code works.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```norun
 | 
			
		||||
fn rect(origin) {
 | 
			
		||||
  return startSketchOn('XZ')
 | 
			
		||||
    |> startProfileAt(origin, %)
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,8 @@
 | 
			
		||||
use std::collections::{BTreeMap, HashMap};
 | 
			
		||||
use std::{
 | 
			
		||||
    collections::{BTreeMap, HashMap},
 | 
			
		||||
    fs::File,
 | 
			
		||||
    io::Read as _,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use base64::Engine;
 | 
			
		||||
@ -7,15 +11,18 @@ use handlebars::Renderable;
 | 
			
		||||
use indexmap::IndexMap;
 | 
			
		||||
use itertools::Itertools;
 | 
			
		||||
use serde_json::json;
 | 
			
		||||
use tokio::task::JoinSet;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    docs::{is_primitive, StdLibFn},
 | 
			
		||||
    std::StdLib,
 | 
			
		||||
    ExecutorContext,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::kcl_doc::{ConstData, DocData, FnData};
 | 
			
		||||
 | 
			
		||||
const TYPES_DIR: &str = "../../../docs/kcl/types";
 | 
			
		||||
const LANG_TOPICS: [&str; 4] = ["Types", "Modules", "Settings", "Known Issues"];
 | 
			
		||||
 | 
			
		||||
fn init_handlebars() -> Result<handlebars::Handlebars<'static>> {
 | 
			
		||||
    let mut hbs = handlebars::Handlebars::new();
 | 
			
		||||
@ -345,7 +352,18 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
 | 
			
		||||
        .collect();
 | 
			
		||||
    sorted.sort_by(|t1, t2| t1.0.cmp(&t2.0));
 | 
			
		||||
    let data: Vec<_> = sorted.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,
 | 
			
		||||
        "modules": data,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -991,3 +1009,67 @@ fn test_generate_stdlib_json_schema() {
 | 
			
		||||
        &serde_json::to_string_pretty(&json_data).unwrap(),
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[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 mut file = File::open(&filename).unwrap();
 | 
			
		||||
        let mut text = String::new();
 | 
			
		||||
        file.read_to_string(&mut text).unwrap();
 | 
			
		||||
 | 
			
		||||
        for (i, (eg, attr)) in find_examples(&text, &filename).into_iter().enumerate() {
 | 
			
		||||
            if attr == "norun" {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let f = filename.clone();
 | 
			
		||||
            join_set.spawn(async move { (format!("{f}, example {i}"), run_example(&eg).await) });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    let results: Vec<_> = join_set
 | 
			
		||||
        .join_all()
 | 
			
		||||
        .await
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .filter_map(|a| a.1.err().map(|e| format!("{}: {}", a.0, e)))
 | 
			
		||||
        .collect();
 | 
			
		||||
    assert!(results.is_empty(), "Failures: {}", results.join(", "))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn find_examples(text: &str, filename: &str) -> Vec<(String, String)> {
 | 
			
		||||
    let mut buf = String::new();
 | 
			
		||||
    let mut attr = String::new();
 | 
			
		||||
    let mut in_eg = false;
 | 
			
		||||
    let mut result = Vec::new();
 | 
			
		||||
    for line in text.lines() {
 | 
			
		||||
        if let Some(rest) = line.strip_prefix("```") {
 | 
			
		||||
            if in_eg {
 | 
			
		||||
                result.push((buf, attr));
 | 
			
		||||
                buf = String::new();
 | 
			
		||||
                attr = String::new();
 | 
			
		||||
                in_eg = false;
 | 
			
		||||
            } else {
 | 
			
		||||
                attr = rest.to_owned();
 | 
			
		||||
                in_eg = true;
 | 
			
		||||
            }
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if in_eg {
 | 
			
		||||
            buf.push('\n');
 | 
			
		||||
            buf.push_str(line)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assert!(!in_eg, "Unclosed code tags in {}", filename);
 | 
			
		||||
 | 
			
		||||
    result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn run_example(text: &str) -> Result<()> {
 | 
			
		||||
    let program = crate::Program::parse_no_errs(text)?;
 | 
			
		||||
    let ctx = ExecutorContext::new_with_default_client(crate::UnitLength::Mm).await?;
 | 
			
		||||
    let mut exec_state = crate::execution::ExecState::new(&ctx.settings);
 | 
			
		||||
    ctx.run(&program, &mut exec_state).await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								src/wasm-lib/kcl/src/docs/templates/index.hbs
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								src/wasm-lib/kcl/src/docs/templates/index.hbs
									
									
									
									
										vendored
									
									
								
							@ -6,9 +6,14 @@ layout: manual
 | 
			
		||||
 | 
			
		||||
## Table of Contents
 | 
			
		||||
 | 
			
		||||
* [Types](kcl/types)
 | 
			
		||||
* [Modules](kcl/modules)
 | 
			
		||||
* [Known Issues](kcl/KNOWN-ISSUES)
 | 
			
		||||
### Language
 | 
			
		||||
 | 
			
		||||
{{#each lang_topics}}
 | 
			
		||||
* [`{{name}}`](kcl/{{file_name}})
 | 
			
		||||
{{/each}}
 | 
			
		||||
 | 
			
		||||
### Standard library
 | 
			
		||||
 | 
			
		||||
{{#each modules}}
 | 
			
		||||
* **`{{name}}`**
 | 
			
		||||
{{#each functions}}
 | 
			
		||||
 | 
			
		||||
@ -65,7 +65,7 @@ impl UnitLen {
 | 
			
		||||
            "yd" => Ok(UnitLen::Yards),
 | 
			
		||||
            value => Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
                message: format!(
 | 
			
		||||
                    "Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `inch`, `ft`, `yd`"
 | 
			
		||||
                    "Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"
 | 
			
		||||
                ),
 | 
			
		||||
                source_ranges: vec![source_range],
 | 
			
		||||
            })),
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user