diff --git a/modeling-cmds/openapi/api.json b/modeling-cmds/openapi/api.json index 2529ea6d..2a9e2e23 100644 --- a/modeling-cmds/openapi/api.json +++ b/modeling-cmds/openapi/api.json @@ -2577,7 +2577,7 @@ ] }, "KclFile": { - "description": "Region-creation algorithm version.", + "description": "A file in a KCL project.", "type": "object", "properties": { "contents": { @@ -2604,7 +2604,7 @@ ] }, "KclProject": { - "description": "A KCL project that can be executed.", + "description": "A KCL project.", "type": "object", "properties": { "entrypoint": { @@ -4134,6 +4134,15 @@ } ] }, + "kcl_source": { + "nullable": true, + "description": "KCL source to embed in formats that support source metadata.", + "allOf": [ + { + "$ref": "#/components/schemas/KclProject" + } + ] + }, "type": { "type": "string", "enum": [ @@ -4167,6 +4176,15 @@ } ] }, + "kcl_source": { + "nullable": true, + "description": "KCL source to embed in formats that support source metadata.", + "allOf": [ + { + "$ref": "#/components/schemas/KclProject" + } + ] + }, "type": { "type": "string", "enum": [ diff --git a/modeling-cmds/src/def_enum.rs b/modeling-cmds/src/def_enum.rs index 699b5bfd..ea0e9e0a 100644 --- a/modeling-cmds/src/def_enum.rs +++ b/modeling-cmds/src/def_enum.rs @@ -23,6 +23,7 @@ define_modeling_cmd_enum! { format::{OutputFormat2d, OutputFormat3d}, id::ModelingCmdId, length_unit::LengthUnit, + kcl_project::KclProject, shared::{ Angle, RegionVersion, @@ -692,6 +693,9 @@ define_modeling_cmd_enum! { pub entity_ids: Vec, /// The file format to export to. pub format: OutputFormat3d, + /// KCL source to embed in formats that support source metadata. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kcl_source: Option, } /// Export the scene to a file. @@ -708,6 +712,9 @@ define_modeling_cmd_enum! { pub entity_ids: Vec, /// The file format to export to. pub format: OutputFormat3d, + /// KCL source to embed in formats that support source metadata. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kcl_source: Option, } /// What is this entity's parent? diff --git a/modeling-cmds/src/exec_kcl.rs b/modeling-cmds/src/exec_kcl.rs index 24426af8..8bab5307 100644 --- a/modeling-cmds/src/exec_kcl.rs +++ b/modeling-cmds/src/exec_kcl.rs @@ -7,62 +7,7 @@ use kcl_error::{CompilationIssue, KclError}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::shared::safe_filepath::SafeFilepath; - -/// A KCL project that can be executed. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Default, Builder)] -#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] -#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)] -pub struct KclProject { - /// All files in the project. - pub files: Vec, - /// Which file is the entrypoint? - /// This is the first KCL file to be executed, - /// the root of the KCL module tree. - pub entrypoint: SafeFilepath, -} - -impl KclProject { - /// Create a new KCL project. - pub fn new(files: Vec, entrypoint: SafeFilepath) -> Self { - Self { files, entrypoint } - } -} - -/// Region-creation algorithm version. -#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, Default, Builder)] -#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] -#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)] -pub struct KclFile { - /// Where is the file, relative to the project directory? - pub path: SafeFilepath, - /// Contents of the file, as UTF-8 encoded bytes. - #[serde( - serialize_with = "serde_bytes::serialize", - deserialize_with = "serde_bytes::deserialize" - )] - pub contents: Vec, -} - -impl std::fmt::Debug for KclFile { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("KclFile") - .field("path", &self.path) - .field("contents.len()", &self.contents.len()) - .finish() - } -} - -impl KclFile { - /// Create a KCL file. - pub fn new(path: SafeFilepath, contents: Vec) -> Self { - Self { path, contents } - } -} +pub use crate::kcl_project::{KclFile, KclFileBuilder, KclProject, KclProjectBuilder}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Builder)] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] diff --git a/modeling-cmds/src/kcl_project.rs b/modeling-cmds/src/kcl_project.rs new file mode 100644 index 00000000..a924d20d --- /dev/null +++ b/modeling-cmds/src/kcl_project.rs @@ -0,0 +1,59 @@ +use crate::shared::safe_filepath::SafeFilepath; +use bon::Builder; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// A KCL project. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Default, Builder)] +#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] +#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)] +pub struct KclProject { + /// All files in the project. + pub files: Vec, + /// Which file is the entrypoint? + /// This is the first KCL file to be executed, + /// the root of the KCL module tree. + pub entrypoint: SafeFilepath, +} + +impl KclProject { + /// Create a new KCL project. + pub fn new(files: Vec, entrypoint: SafeFilepath) -> Self { + Self { files, entrypoint } + } +} + +/// A file in a KCL project. +#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, Default, Builder)] +#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] +#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)] +pub struct KclFile { + /// Where is the file, relative to the project directory? + pub path: SafeFilepath, + /// Contents of the file, as UTF-8 encoded bytes. + #[serde( + serialize_with = "serde_bytes::serialize", + deserialize_with = "serde_bytes::deserialize" + )] + pub contents: Vec, +} + +impl std::fmt::Debug for KclFile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KclFile") + .field("path", &self.path) + .field("contents.len()", &self.contents.len()) + .finish() + } +} + +impl KclFile { + /// Create a KCL file. + pub fn new(path: SafeFilepath, contents: Vec) -> Self { + Self { path, contents } + } +} diff --git a/modeling-cmds/src/lib.rs b/modeling-cmds/src/lib.rs index f7f878d3..607474ec 100644 --- a/modeling-cmds/src/lib.rs +++ b/modeling-cmds/src/lib.rs @@ -39,6 +39,9 @@ pub mod shared; #[cfg(feature = "exec-kcl")] pub mod exec_kcl; +/// Structure of KCL projects (one or more KCL files). +pub mod kcl_project; + #[cfg(all(test, feature = "derive-jsonschema-on-enums"))] mod tests; diff --git a/modeling-cmds/src/shared.rs b/modeling-cmds/src/shared.rs index 4db11c9c..33ba12ce 100644 --- a/modeling-cmds/src/shared.rs +++ b/modeling-cmds/src/shared.rs @@ -18,6 +18,8 @@ use crate::{ pub mod safe_filepath; +pub use crate::kcl_project::{KclFile, KclFileBuilder, KclProject, KclProjectBuilder}; + /// Default tolerance values for modeling operations. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Builder)] #[serde(rename_all = "snake_case")] diff --git a/modeling-cmds/tests/kcl_project_compat.rs b/modeling-cmds/tests/kcl_project_compat.rs new file mode 100644 index 00000000..8b46e76c --- /dev/null +++ b/modeling-cmds/tests/kcl_project_compat.rs @@ -0,0 +1,44 @@ +//! Checks that moving KCL project types preserves consumers' existing import paths. + +use kittycad_modeling_cmds::{kcl_project, shared}; + +#[test] +fn shared_project_types_and_builders_remain_compatible() { + let file_builder: shared::KclFileBuilder = kcl_project::KclFile::builder(); + let file: shared::KclFile = file_builder + .path("main.kcl".parse().unwrap()) + .contents(b"// KCL source".to_vec()) + .build(); + let file: kcl_project::KclFile = file; + + let project_builder: shared::KclProjectBuilder = kcl_project::KclProject::builder(); + let project: shared::KclProject = project_builder + .files(vec![file]) + .entrypoint("main.kcl".parse().unwrap()) + .build(); + let project: kcl_project::KclProject = project; + + assert_eq!(project.entrypoint, project.files[0].path); +} + +#[cfg(feature = "exec-kcl")] +#[test] +fn exec_kcl_project_types_and_builders_remain_compatible() { + use kittycad_modeling_cmds::exec_kcl; + + let file_builder: exec_kcl::KclFileBuilder = kcl_project::KclFile::builder(); + let file: exec_kcl::KclFile = file_builder + .path("main.kcl".parse().unwrap()) + .contents(b"// KCL source".to_vec()) + .build(); + let file: kcl_project::KclFile = file; + + let project_builder: exec_kcl::KclProjectBuilder = kcl_project::KclProject::builder(); + let project: exec_kcl::KclProject = project_builder + .files(vec![file]) + .entrypoint("main.kcl".parse().unwrap()) + .build(); + let project: kcl_project::KclProject = project; + + assert_eq!(project.entrypoint, project.files[0].path); +}