more updates for kcl-samples (#5696)

* screenshots and step

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* automations

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add manifest generation

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* small refactor

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* update readme

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* write the readme

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes for comments

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* derive-docs tests updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* update all the generated artifact commands, since we dont need to clear scene at the start of run so we dont need to recreate all the planes

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>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-03-07 18:45:33 -08:00
committed by GitHub
parent be69039d40
commit bc3a0e3896
318 changed files with 393583 additions and 29897 deletions

View File

@ -4,10 +4,14 @@
//! the samples, in this case, all those that start with "gear".
use std::{
collections::HashMap,
fs,
io::Write,
path::{Path, PathBuf},
};
use anyhow::Result;
use fnv::FnvHashSet;
use serde::{Deserialize, Serialize};
use tokio::task::JoinSet;
use super::Test;
@ -73,7 +77,7 @@ async fn kcl_test_execute() {
// Spawn a task for each test.
for (index, test) in tests.iter().cloned().enumerate() {
let handle = tasks.spawn(async move {
super::execute_test(&test, true).await;
super::execute_test(&test, true, true).await;
});
id_to_index.insert(handle.id(), index);
}
@ -99,6 +103,53 @@ async fn kcl_test_execute() {
.filter(|name| !input_names.contains(name))
.collect::<Vec<_>>();
assert!(missing.is_empty(), "Expected input kcl-samples for the following. If these are no longer tests, delete the expected output directories for them in {}: {missing:?}", OUTPUTS_DIR.to_string_lossy());
// We want to move the step and screenshot for the inputs to the public/kcl-samples
// directory so that they can be used as inputs for the next run.
// First ensure each directory exists.
let public_screenshot_dir = INPUTS_DIR.join("screenshots");
let public_step_dir = INPUTS_DIR.join("step");
for dir in [&public_step_dir, &public_screenshot_dir] {
if !dir.exists() {
std::fs::create_dir_all(dir).unwrap();
}
}
for tests in &tests {
let screenshot_file = OUTPUTS_DIR.join(&tests.name).join(super::RENDERED_MODEL_NAME);
if !screenshot_file.exists() {
panic!("Missing screenshot for test: {}", tests.name);
}
std::fs::copy(
screenshot_file,
public_screenshot_dir.join(format!("{}.png", &tests.name)),
)
.unwrap();
let step_file = OUTPUTS_DIR.join(&tests.name).join("exported_step.snap.step");
if !step_file.exists() {
panic!("Missing step for test: {}", tests.name);
}
std::fs::copy(step_file, public_step_dir.join(format!("{}.step", &tests.name))).unwrap();
}
// Update the README.md with the new screenshots and steps.
let mut new_content = String::new();
for test in tests {
// Format:
new_content.push_str(&format!(
r#"#### [{}]({}/main.kcl) ([step](step/{}.step)) ([screenshot](screenshots/{}.png))
[![{}](screenshots/{}.png)]({}/main.kcl)
"#,
test.name, test.name, test.name, test.name, test.name, test.name, test.name
));
}
update_readme(&INPUTS_DIR, &new_content).unwrap();
}
#[test]
fn generate_manifest() {
// Generate the manifest.json
generate_kcl_manifest(&INPUTS_DIR).unwrap();
}
fn test(test_name: &str, entry_point: String) -> Test {
@ -116,8 +167,19 @@ fn filter_from_env() -> Option<String> {
fn kcl_samples_inputs(filter: Option<&str>) -> Vec<Test> {
let mut tests = Vec::new();
for entry in INPUTS_DIR.read_dir().unwrap() {
let entry = entry.unwrap();
// Collect all directory entries first and sort them by name for consistent ordering
let mut entries: Vec<_> = INPUTS_DIR
.read_dir()
.unwrap()
.filter_map(Result::ok)
.filter(|e| e.path().is_dir())
.collect();
// Sort directories by name for consistent ordering
entries.sort_by_key(|a| a.file_name());
for entry in entries {
let path = entry.path();
if !path.is_dir() {
// We're looking for directories only.
@ -146,7 +208,7 @@ fn kcl_samples_inputs(filter: Option<&str>) -> Vec<Test> {
let entry_point = if sub_dir.join("main.kcl").exists() {
"main.kcl".to_owned()
} else {
format!("{dir_name_str}.kcl")
panic!("No main.kcl found in {:?}", sub_dir);
};
tests.push(test(&dir_name_str, entry_point));
}
@ -184,3 +246,152 @@ fn kcl_samples_outputs(filter: Option<&str>) -> Vec<String> {
outputs
}
const MANIFEST_FILE: &str = "manifest.json";
const COMMENT_PREFIX: &str = "//";
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct KclMetadata {
file: String,
path_from_project_directory_to_first_file: String,
multiple_files: bool,
title: String,
description: String,
}
// Function to read and parse .kcl files
fn get_kcl_metadata(project_path: &Path, files: &[String]) -> Option<KclMetadata> {
// Find primary kcl file (main.kcl or first sorted file)
let primary_kcl_file = files
.iter()
.find(|file| file.contains("main.kcl"))
.unwrap_or_else(|| files.iter().min().unwrap())
.clone();
let full_path_to_primary_kcl = project_path.join(&primary_kcl_file);
// Read the file content
let content = match fs::read_to_string(&full_path_to_primary_kcl) {
Ok(content) => content,
Err(_) => return None,
};
let lines: Vec<&str> = content.lines().collect();
if lines.len() < 2 {
return None;
}
// Extract title and description from the first two lines
let title = lines[0].trim_start_matches(COMMENT_PREFIX).trim().to_string();
let description = lines[1].trim_start_matches(COMMENT_PREFIX).trim().to_string();
// Get the path components
let path_components: Vec<String> = full_path_to_primary_kcl
.components()
.map(|comp| comp.as_os_str().to_string_lossy().to_string())
.collect();
// Get the last two path components
let len = path_components.len();
let path_from_project_dir = if len >= 2 {
format!("{}/{}", path_components[len - 2], path_components[len - 1])
} else {
primary_kcl_file.clone()
};
Some(KclMetadata {
file: primary_kcl_file,
path_from_project_directory_to_first_file: path_from_project_dir,
multiple_files: files.len() > 1,
title,
description,
})
}
// Function to scan the directory and generate the manifest.json
fn generate_kcl_manifest(dir: &Path) -> Result<()> {
let mut manifest = Vec::new();
// Collect all directory entries first and sort them by name for consistent ordering
let mut entries: Vec<_> = fs::read_dir(dir)?
.filter_map(Result::ok)
.filter(|e| e.path().is_dir())
.collect();
// Sort directories by name for consistent ordering
entries.sort_by_key(|a| a.file_name());
for entry in entries {
let project_path = entry.path();
if project_path.is_dir() {
// Get all .kcl files in the directory
let files: Vec<String> = fs::read_dir(&project_path)?
.filter_map(Result::ok)
.filter(|e| {
if let Some(ext) = e.path().extension() {
ext == "kcl"
} else {
false
}
})
.map(|e| e.file_name().to_string_lossy().to_string())
.collect();
if files.is_empty() {
continue;
}
if let Some(metadata) = get_kcl_metadata(&project_path, &files) {
manifest.push(metadata);
}
}
}
// Write the manifest.json
let output_path = dir.join(MANIFEST_FILE);
let manifest_json = serde_json::to_string_pretty(&manifest)?;
let mut file = fs::File::create(output_path.clone())?;
file.write_all(manifest_json.as_bytes())?;
println!(
"Manifest of {} items written to {}",
manifest.len(),
output_path.display()
);
Ok(())
}
/// Updates README.md by finding a specific search string and replacing all content after it
/// with the new content provided.
fn update_readme(dir: &Path, new_content: &str) -> Result<()> {
let search_str = "---\n";
let readme_path = dir.join("README.md");
// Read the file content
let content = fs::read_to_string(&readme_path)?;
// Find the line containing the search string
let Some(index) = content.find(search_str) else {
anyhow::bail!(
"Search string '{}' not found in `{}`",
search_str,
readme_path.display()
);
};
// Get the position just after the search string
let position = index + search_str.len();
// Create the updated content
let updated_content = format!("{}{}\n", &content[..position], new_content);
// Write the modified content back to the file
std::fs::write(readme_path, updated_content)?;
Ok(())
}