generate the settings docs (#5740)
* generate the settings docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * Update docs/kcl/settings.md --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
232
rust/kcl-lib/src/settings/generate_settings_docs.rs
Normal file
232
rust/kcl-lib/src/settings/generate_settings_docs.rs
Normal file
@ -0,0 +1,232 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use schemars::{gen::SchemaGenerator, JsonSchema};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::settings::types::{project::ProjectConfiguration, Configuration};
|
||||
|
||||
// Project settings example in TOML format
|
||||
const PROJECT_SETTINGS_EXAMPLE: &str = r#"[settings.app]
|
||||
# Set the appearance of the application
|
||||
name = "My Awesome Project"
|
||||
|
||||
[settings.app.appearance]
|
||||
# Use dark mode theme
|
||||
theme = "dark"
|
||||
# Set the app color to blue (240.0 = blue, 0.0 = red, 120.0 = green)
|
||||
color = 240.0
|
||||
|
||||
[settings.modeling]
|
||||
# Use inches as the default measurement unit
|
||||
base_unit = "in"
|
||||
"#;
|
||||
|
||||
// User settings example in TOML format
|
||||
const USER_SETTINGS_EXAMPLE: &str = r#"[settings.app]
|
||||
# Set the appearance of the application
|
||||
[settings.app.appearance]
|
||||
# Use dark mode theme
|
||||
theme = "dark"
|
||||
# Set the app color to blue (240.0 = blue, 0.0 = red, 120.0 = green)
|
||||
color = 240.0
|
||||
|
||||
[settings.modeling]
|
||||
# Use millimeters as the default measurement unit
|
||||
base_unit = "mm"
|
||||
|
||||
[settings.text_editor]
|
||||
# Disable text wrapping in the editor
|
||||
text_wrapping = false
|
||||
"#;
|
||||
|
||||
const PROJECT_SETTINGS_DOC_PATH: &str = "../../docs/kcl/settings/project.toml.md";
|
||||
const USER_SETTINGS_DOC_PATH: &str = "../../docs/kcl/settings/user.toml.md";
|
||||
|
||||
fn init_handlebars() -> handlebars::Handlebars<'static> {
|
||||
let mut hbs = handlebars::Handlebars::new();
|
||||
|
||||
// Register helper to pretty-format enum values
|
||||
hbs.register_helper(
|
||||
"pretty_enum",
|
||||
Box::new(
|
||||
|h: &handlebars::Helper,
|
||||
_: &handlebars::Handlebars,
|
||||
_: &handlebars::Context,
|
||||
_: &mut handlebars::RenderContext,
|
||||
out: &mut dyn handlebars::Output|
|
||||
-> handlebars::HelperResult {
|
||||
if let Some(enum_value) = h.param(0) {
|
||||
if let Some(array) = enum_value.value().as_array() {
|
||||
let pretty_options = array
|
||||
.iter()
|
||||
.filter_map(|v| v.as_str())
|
||||
.map(|s| format!("`{}`", s))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
out.write(&pretty_options)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
out.write("No options available")?;
|
||||
Ok(())
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Helper to format default values better
|
||||
hbs.register_helper(
|
||||
"format_default",
|
||||
Box::new(
|
||||
|h: &handlebars::Helper,
|
||||
_: &handlebars::Handlebars,
|
||||
_: &handlebars::Context,
|
||||
_: &mut handlebars::RenderContext,
|
||||
out: &mut dyn handlebars::Output|
|
||||
-> handlebars::HelperResult {
|
||||
if let Some(default) = h.param(0) {
|
||||
let val = default.value();
|
||||
match val {
|
||||
Value::Null => out.write("None")?,
|
||||
Value::Bool(b) => out.write(&b.to_string())?,
|
||||
Value::Number(n) => out.write(&n.to_string())?,
|
||||
Value::String(s) => out.write(&format!("`{}`", s))?,
|
||||
Value::Array(arr) => {
|
||||
let formatted = arr
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
Value::String(s) => format!("`{}`", s),
|
||||
_ => format!("{}", v),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
out.write(&format!("[{}]", formatted))?;
|
||||
}
|
||||
Value::Object(_) => out.write("(complex default)")?,
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
out.write("None")?;
|
||||
Ok(())
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Register the settings template
|
||||
hbs.register_template_string("settings", include_str!("templates/settings.hbs"))
|
||||
.expect("Failed to register settings template");
|
||||
|
||||
hbs
|
||||
}
|
||||
|
||||
fn ensure_settings_dir() {
|
||||
let settings_dir = PathBuf::from("../../docs/kcl/settings");
|
||||
if !settings_dir.exists() {
|
||||
fs::create_dir_all(&settings_dir).expect("Failed to create settings directory");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_settings_docs() {
|
||||
ensure_settings_dir();
|
||||
let hbs = init_handlebars();
|
||||
|
||||
// Generate project settings documentation
|
||||
let mut settings = schemars::gen::SchemaSettings::default();
|
||||
settings.inline_subschemas = true;
|
||||
settings.meta_schema = None; // We don't need the meta schema for docs
|
||||
settings.option_nullable = false; // Important - makes Option fields show properly
|
||||
settings.option_add_null_type = false;
|
||||
|
||||
let mut generator = SchemaGenerator::new(settings.clone());
|
||||
let project_schema = ProjectConfiguration::json_schema(&mut generator);
|
||||
|
||||
// For debugging the schema:
|
||||
// fs::write("/tmp/project_schema.json", serde_json::to_string_pretty(&project_schema).unwrap())
|
||||
// .expect("Failed to write debug schema");
|
||||
|
||||
// Extract the description from the schema metadata
|
||||
let project_description = if let schemars::schema::Schema::Object(obj) = &project_schema {
|
||||
if let Some(metadata) = &obj.metadata {
|
||||
metadata.description.clone().unwrap_or_default()
|
||||
} else {
|
||||
"Project specific settings for the KittyCAD modeling app.".to_string()
|
||||
}
|
||||
} else {
|
||||
"Project specific settings for the KittyCAD modeling app.".to_string()
|
||||
};
|
||||
|
||||
// Convert the schema to our template format
|
||||
let project_data = json!({
|
||||
"title": "Project Settings",
|
||||
"description": project_description,
|
||||
"config_type": "Project Configuration",
|
||||
"file_name": "project.toml",
|
||||
"settings": json!(project_schema),
|
||||
"example": PROJECT_SETTINGS_EXAMPLE
|
||||
});
|
||||
|
||||
let project_output = hbs
|
||||
.render("settings", &project_data)
|
||||
.expect("Failed to render project settings documentation");
|
||||
|
||||
expectorate::assert_contents(PROJECT_SETTINGS_DOC_PATH, &project_output);
|
||||
|
||||
// Generate user settings documentation
|
||||
let mut generator = SchemaGenerator::new(settings);
|
||||
let user_schema = Configuration::json_schema(&mut generator);
|
||||
|
||||
// For debugging the schema:
|
||||
// fs::write("/tmp/user_schema.json", serde_json::to_string_pretty(&user_schema).unwrap())
|
||||
// .expect("Failed to write debug schema");
|
||||
|
||||
// Extract the description from the schema metadata
|
||||
let user_description = if let schemars::schema::Schema::Object(obj) = &user_schema {
|
||||
if let Some(metadata) = &obj.metadata {
|
||||
metadata.description.clone().unwrap_or_default()
|
||||
} else {
|
||||
"User-specific configuration options for the KittyCAD modeling app.".to_string()
|
||||
}
|
||||
} else {
|
||||
"User-specific configuration options for the KittyCAD modeling app.".to_string()
|
||||
};
|
||||
|
||||
// Trim any trailing periods to avoid double periods
|
||||
|
||||
let user_data = json!({
|
||||
"title": "User Settings",
|
||||
"description": user_description,
|
||||
"config_type": "User Configuration",
|
||||
"file_name": "user.toml",
|
||||
"settings": json!(user_schema),
|
||||
"example": USER_SETTINGS_EXAMPLE
|
||||
});
|
||||
|
||||
let user_output = hbs
|
||||
.render("settings", &user_data)
|
||||
.expect("Failed to render user settings documentation");
|
||||
|
||||
expectorate::assert_contents(USER_SETTINGS_DOC_PATH, &user_output);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_generate_settings_docs() {
|
||||
// First verify that our TOML examples are valid and match the expected types
|
||||
let _project_config: ProjectConfiguration = toml::from_str(PROJECT_SETTINGS_EXAMPLE)
|
||||
.expect("Project settings example is not valid according to ProjectConfiguration");
|
||||
let _user_config: Configuration = toml::from_str(USER_SETTINGS_EXAMPLE)
|
||||
.expect("User settings example is not valid according to Configuration");
|
||||
|
||||
// Expectorate will verify the output matches what we expect,
|
||||
// or update it if run with EXPECTORATE=overwrite
|
||||
generate_settings_docs();
|
||||
|
||||
// Verify files exist
|
||||
let project_path = PathBuf::from(PROJECT_SETTINGS_DOC_PATH);
|
||||
let user_path = PathBuf::from(USER_SETTINGS_DOC_PATH);
|
||||
assert!(project_path.exists(), "Project settings documentation not generated");
|
||||
assert!(user_path.exists(), "User settings documentation not generated");
|
||||
}
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
//! This module contains settings for kcl projects as well as the modeling app.
|
||||
|
||||
pub mod types;
|
||||
|
||||
#[cfg(test)]
|
||||
mod generate_settings_docs;
|
||||
|
67
rust/kcl-lib/src/settings/templates/settings.hbs
Normal file
67
rust/kcl-lib/src/settings/templates/settings.hbs
Normal file
@ -0,0 +1,67 @@
|
||||
---
|
||||
title: "{{title}}"
|
||||
excerpt: "{{{description}}}"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
# {{title}}
|
||||
|
||||
{{{description}}}
|
||||
|
||||
## {{config_type}} Structure
|
||||
|
||||
```toml
|
||||
{{{example}}}
|
||||
```
|
||||
|
||||
## Available Settings
|
||||
|
||||
{{#with settings.properties}}
|
||||
{{#each this}}
|
||||
### {{@key}}
|
||||
|
||||
{{#if metadata.description}}{{metadata.description}}{{/if}}
|
||||
|
||||
{{#with properties}}
|
||||
{{#each this}}
|
||||
#### {{@key}}
|
||||
|
||||
{{#if description}}{{description}}{{/if}}
|
||||
|
||||
{{#if enum}}
|
||||
**Possible values:** {{pretty_enum enum}}
|
||||
{{/if}}
|
||||
|
||||
**Default:** {{#if default}}{{format_default default}}{{else}}None{{/if}}
|
||||
|
||||
{{#if properties}}
|
||||
This setting has the following nested options:
|
||||
|
||||
{{#each properties}}
|
||||
##### {{@key}}
|
||||
|
||||
{{#if description}}{{description}}{{/if}}
|
||||
|
||||
{{#if enum}}
|
||||
**Possible values:** {{pretty_enum enum}}
|
||||
{{/if}}
|
||||
|
||||
**Default:** {{#if default}}{{format_default default}}{{else}}None{{/if}}
|
||||
|
||||
{{#if properties}}
|
||||
This setting has further nested options. See the schema for full details.
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{/each}}
|
||||
{{/with}}
|
||||
|
||||
{{/each}}
|
||||
{{/with}}
|
||||
|
||||
## Complete Example
|
||||
|
||||
```toml
|
||||
{{{example}}}
|
||||
```
|
@ -11,7 +11,11 @@ use validator::{Validate, ValidateRange};
|
||||
const DEFAULT_THEME_COLOR: f64 = 264.5;
|
||||
const DEFAULT_PROJECT_NAME_TEMPLATE: &str = "project-$nnn";
|
||||
|
||||
/// High level configuration.
|
||||
/// User specific settings for the app.
|
||||
/// These live in `user.toml` in the app's configuration directory.
|
||||
/// Updating the settings in the app will update this file automatically.
|
||||
/// Do not edit this file manually, as it may be overwritten by the app.
|
||||
/// Manual edits can cause corruption of the settings file.
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
@ -10,7 +10,11 @@ use crate::settings::types::{
|
||||
is_default, AppColor, CommandBarSettings, DefaultTrue, FloatOrInt, OnboardingStatus, TextEditorSettings, UnitLength,
|
||||
};
|
||||
|
||||
/// High level project configuration.
|
||||
/// Project specific settings for the app.
|
||||
/// These live in `project.toml` in the base of the project directory.
|
||||
/// Updating the settings for the project in the app will update this file automatically.
|
||||
/// Do not edit this file manually, as it may be overwritten by the app.
|
||||
/// Manual edits can cause corruption of the settings file.
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
Reference in New Issue
Block a user