test the wasm side (#6726)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
@ -676,7 +676,9 @@ extrude(profile001, length = 100)"#
|
||||
std::fs::write(tmp_file, other_file.1).unwrap();
|
||||
|
||||
let ExecTestResults { program, exec_ctxt, .. } =
|
||||
parse_execute_with_project_dir(code, Some(tmp_dir)).await.unwrap();
|
||||
parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut new_program = crate::Program::parse_no_errs(code).unwrap();
|
||||
new_program.compute_digest();
|
||||
@ -755,7 +757,9 @@ extrude(profile001, length = 100)
|
||||
std::fs::write(&tmp_file, other_file.1).unwrap();
|
||||
|
||||
let ExecTestResults { program, exec_ctxt, .. } =
|
||||
parse_execute_with_project_dir(code, Some(tmp_dir)).await.unwrap();
|
||||
parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Change the other file.
|
||||
std::fs::write(tmp_file, other_file2.1).unwrap();
|
||||
|
@ -2643,7 +2643,7 @@ d = b + c
|
||||
)),
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
settings: ExecutorSettings {
|
||||
project_directory: Some(tmpdir.path().into()),
|
||||
project_directory: Some(crate::TypedPath(tmpdir.path().into())),
|
||||
..Default::default()
|
||||
},
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{ffi::OsStr, path::Path, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use kcmc::{
|
||||
@ -15,7 +15,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{annotations, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry},
|
||||
execution::{annotations, typed_path::TypedPath, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry},
|
||||
fs::FileSystem,
|
||||
parsing::ast::types::{Annotation, Node},
|
||||
source_range::SourceRange,
|
||||
@ -29,7 +29,7 @@ use crate::{
|
||||
pub const ZOO_COORD_SYSTEM: System = *KITTYCAD;
|
||||
|
||||
pub async fn import_foreign(
|
||||
file_path: &Path,
|
||||
file_path: &TypedPath,
|
||||
format: Option<InputFormat3d>,
|
||||
exec_state: &mut ExecState,
|
||||
ctxt: &ExecutorContext,
|
||||
@ -43,19 +43,18 @@ pub async fn import_foreign(
|
||||
}));
|
||||
}
|
||||
|
||||
let ext_format =
|
||||
get_import_format_from_extension(file_path.extension().and_then(OsStr::to_str).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("No file extension found for `{}`", file_path.display()),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?)
|
||||
.map_err(|e| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: e.to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("No file extension found for `{}`", file_path.display()),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?)
|
||||
.map_err(|e| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: e.to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
// Get the format type from the extension of the file.
|
||||
let format = if let Some(format) = format {
|
||||
@ -80,15 +79,12 @@ pub async fn import_foreign(
|
||||
})?;
|
||||
|
||||
// We want the file_path to be without the parent.
|
||||
let file_name = std::path::Path::new(&file_path)
|
||||
.file_name()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Could not get the file name from the path `{}`", file_path.display()),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let file_name = file_path.file_name().map(|p| p.to_string()).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Could not get the file name from the path `{}`", file_path.display()),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let mut import_files = vec![kcmc::ImportFile {
|
||||
path: file_name.to_string(),
|
||||
data: file_contents.clone(),
|
||||
@ -112,19 +108,12 @@ pub async fn import_foreign(
|
||||
if let Some(uri) = &buffer.uri {
|
||||
if !uri.starts_with("data:") {
|
||||
// We want this path relative to the file_path given.
|
||||
let bin_path = std::path::Path::new(&file_path)
|
||||
.parent()
|
||||
.map(|p| p.join(uri))
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Could not get the parent path of the file `{}`",
|
||||
file_path.display()
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let bin_path = file_path.parent().map(|p| p.join(uri)).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Could not get the parent path of the file `{}`", file_path.display()),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let bin_contents = ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
@ -154,7 +143,7 @@ pub async fn import_foreign(
|
||||
|
||||
pub(super) fn format_from_annotations(
|
||||
annotations: &[Node<Annotation>],
|
||||
path: &Path,
|
||||
path: &TypedPath,
|
||||
import_source_range: SourceRange,
|
||||
) -> Result<Option<InputFormat3d>, KclError> {
|
||||
if annotations.is_empty() {
|
||||
@ -184,7 +173,6 @@ pub(super) fn format_from_annotations(
|
||||
let mut result = result
|
||||
.or_else(|| {
|
||||
path.extension()
|
||||
.and_then(OsStr::to_str)
|
||||
.and_then(|ext| get_import_format_from_extension(ext).ok())
|
||||
})
|
||||
.ok_or(KclError::Semantic(KclErrorDetails {
|
||||
@ -397,7 +385,7 @@ mod test {
|
||||
fn annotations() {
|
||||
// no annotations
|
||||
assert!(
|
||||
format_from_annotations(&[], Path::new("../foo.txt"), SourceRange::default(),)
|
||||
format_from_annotations(&[], &TypedPath::from("../foo.txt"), SourceRange::default(),)
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
@ -406,7 +394,7 @@ mod test {
|
||||
let text = "@()\nimport '../foo.gltf' as foo";
|
||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||
let attrs = parsed.body[0].get_attrs();
|
||||
let fmt = format_from_annotations(attrs, Path::new("../foo.gltf"), SourceRange::default())
|
||||
let fmt = format_from_annotations(attrs, &TypedPath::from("../foo.gltf"), SourceRange::default())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@ -418,7 +406,7 @@ mod test {
|
||||
let text = "@(format = gltf)\nimport '../foo.txt' as foo";
|
||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||
let attrs = parsed.body[0].get_attrs();
|
||||
let fmt = format_from_annotations(attrs, Path::new("../foo.txt"), SourceRange::default())
|
||||
let fmt = format_from_annotations(attrs, &TypedPath::from("../foo.txt"), SourceRange::default())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@ -427,7 +415,7 @@ mod test {
|
||||
);
|
||||
|
||||
// format, no extension (wouldn't parse but might some day)
|
||||
let fmt = format_from_annotations(attrs, Path::new("../foo"), SourceRange::default())
|
||||
let fmt = format_from_annotations(attrs, &TypedPath::from("../foo"), SourceRange::default())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@ -439,7 +427,7 @@ mod test {
|
||||
let text = "@(format = obj, coords = vulkan, lengthUnit = ft)\nimport '../foo.txt' as foo";
|
||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||
let attrs = parsed.body[0].get_attrs();
|
||||
let fmt = format_from_annotations(attrs, Path::new("../foo.txt"), SourceRange::default())
|
||||
let fmt = format_from_annotations(attrs, &TypedPath::from("../foo.txt"), SourceRange::default())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@ -454,7 +442,7 @@ mod test {
|
||||
let text = "@(coords = vulkan, lengthUnit = ft)\nimport '../foo.obj' as foo";
|
||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||
let attrs = parsed.body[0].get_attrs();
|
||||
let fmt = format_from_annotations(attrs, Path::new("../foo.obj"), SourceRange::default())
|
||||
let fmt = format_from_annotations(attrs, &TypedPath::from("../foo.obj"), SourceRange::default())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@ -507,7 +495,7 @@ mod test {
|
||||
fn assert_annotation_error(src: &str, path: &str, expected: &str) {
|
||||
let parsed = crate::Program::parse_no_errs(src).unwrap().ast;
|
||||
let attrs = parsed.body[0].get_attrs();
|
||||
let err = format_from_annotations(attrs, Path::new(path), SourceRange::default()).unwrap_err();
|
||||
let err = format_from_annotations(attrs, &TypedPath::from(path), SourceRange::default()).unwrap_err();
|
||||
assert!(
|
||||
err.message().contains(expected),
|
||||
"Expected: `{expected}`, found `{}`",
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! The executor for the AST.
|
||||
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
@ -35,6 +35,7 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
cache::{CacheInformation, CacheResult},
|
||||
typed_path::TypedPath,
|
||||
types::{UnitAngle, UnitLen},
|
||||
},
|
||||
fs::FileManager,
|
||||
@ -59,6 +60,7 @@ mod import;
|
||||
pub(crate) mod kcl_value;
|
||||
mod memory;
|
||||
mod state;
|
||||
pub mod typed_path;
|
||||
pub(crate) mod types;
|
||||
|
||||
/// Outcome of executing a program. This is used in TS.
|
||||
@ -287,10 +289,10 @@ pub struct ExecutorSettings {
|
||||
pub replay: Option<String>,
|
||||
/// The directory of the current project. This is used for resolving import
|
||||
/// paths. If None is given, the current working directory is used.
|
||||
pub project_directory: Option<PathBuf>,
|
||||
pub project_directory: Option<TypedPath>,
|
||||
/// This is the path to the current file being executed.
|
||||
/// We use this for preventing cyclic imports.
|
||||
pub current_file: Option<PathBuf>,
|
||||
pub current_file: Option<TypedPath>,
|
||||
}
|
||||
|
||||
impl Default for ExecutorSettings {
|
||||
@ -360,15 +362,15 @@ impl From<crate::settings::types::project::ProjectModelingSettings> for Executor
|
||||
|
||||
impl ExecutorSettings {
|
||||
/// Add the current file path to the executor settings.
|
||||
pub fn with_current_file(&mut self, current_file: PathBuf) {
|
||||
pub fn with_current_file(&mut self, current_file: TypedPath) {
|
||||
// We want the parent directory of the file.
|
||||
if current_file.extension() == Some(std::ffi::OsStr::new("kcl")) {
|
||||
if current_file.extension() == Some("kcl") {
|
||||
self.current_file = Some(current_file.clone());
|
||||
// Get the parent directory.
|
||||
if let Some(parent) = current_file.parent() {
|
||||
self.project_directory = Some(parent.to_path_buf());
|
||||
self.project_directory = Some(parent);
|
||||
} else {
|
||||
self.project_directory = Some(std::path::PathBuf::from(""));
|
||||
self.project_directory = Some(TypedPath::from(""));
|
||||
}
|
||||
} else {
|
||||
self.project_directory = Some(current_file.clone());
|
||||
@ -856,7 +858,7 @@ impl ExecutorContext {
|
||||
if universe_map.contains_key(value) {
|
||||
exec_state.global.operations.push(Operation::GroupBegin {
|
||||
group: Group::ModuleInstance {
|
||||
name: value.file_name().unwrap_or_default().to_string_lossy().into_owned(),
|
||||
name: value.file_name().unwrap_or_default(),
|
||||
module_id,
|
||||
},
|
||||
source_range,
|
||||
@ -1306,7 +1308,7 @@ pub(crate) async fn parse_execute(code: &str) -> Result<ExecTestResults, KclErro
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn parse_execute_with_project_dir(
|
||||
code: &str,
|
||||
project_directory: Option<std::path::PathBuf>,
|
||||
project_directory: Option<TypedPath>,
|
||||
) -> Result<ExecTestResults, KclError> {
|
||||
let program = crate::Program::parse_no_errs(code)?;
|
||||
|
||||
|
@ -275,7 +275,7 @@ impl ExecState {
|
||||
.mod_loader
|
||||
.import_stack
|
||||
.iter()
|
||||
.map(|p| p.as_path().to_string_lossy())
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" -> "),
|
||||
path,
|
||||
|
208
rust/kcl-lib/src/execution/typed_path.rs
Normal file
208
rust/kcl-lib/src/execution/typed_path.rs
Normal file
@ -0,0 +1,208 @@
|
||||
//! A typed path type so that in wasm we can track if its a windows or unix path.
|
||||
//! On non-wasm platforms, this is just a std::path::PathBuf.
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TypedPath(
|
||||
#[cfg(target_arch = "wasm32")] pub typed_path::TypedPathBuf,
|
||||
#[cfg(not(target_arch = "wasm32"))] pub std::path::PathBuf,
|
||||
);
|
||||
|
||||
impl std::fmt::Display for TypedPath {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.0.to_path().display().fmt(f)
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.0.display().fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TypedPath {
|
||||
fn default() -> Self {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
TypedPath(typed_path::TypedPath::derive("").to_path_buf())
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
TypedPath(std::path::PathBuf::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for TypedPath {
|
||||
fn from(path: &String) -> Self {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
TypedPath(typed_path::TypedPath::derive(path).to_path_buf())
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
TypedPath(std::path::PathBuf::from(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for TypedPath {
|
||||
fn from(path: &str) -> Self {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
TypedPath(typed_path::TypedPath::derive(path).to_path_buf())
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
TypedPath(std::path::PathBuf::from(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedPath {
|
||||
pub fn extension(&self) -> Option<&str> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.0
|
||||
.extension()
|
||||
.map(|s| std::str::from_utf8(s).map(|s| s.trim_start_matches('.')).unwrap_or(""))
|
||||
.filter(|s| !s.is_empty())
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.0.extension().and_then(|s| s.to_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join(&self, path: &str) -> Self {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
TypedPath(self.0.join(path))
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
TypedPath(self.0.join(path))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<Self> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.0.parent().map(|p| TypedPath(p.to_path_buf()))
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.0.parent().map(|p| TypedPath(p.to_path_buf()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string_lossy(&self) -> String {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.0.to_path().to_string_lossy().to_string()
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.0.to_string_lossy().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self) -> String {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.0.to_path().display().to_string()
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.0.display().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_name(&self) -> Option<String> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.0
|
||||
.file_name()
|
||||
.map(|s| std::str::from_utf8(s).unwrap_or(""))
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.0.file_name().and_then(|s| s.to_str()).map(|s| s.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for TypedPath {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.0.to_str().serialize(serializer)
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::de::Deserialize<'de> for TypedPath {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let path: String = serde::Deserialize::deserialize(deserializer)?;
|
||||
Ok(TypedPath(typed_path::TypedPath::derive(&path).to_path_buf()))
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let path: std::path::PathBuf = serde::Deserialize::deserialize(deserializer)?;
|
||||
Ok(TypedPath(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ts_rs::TS for TypedPath {
|
||||
type WithoutGenerics = Self;
|
||||
|
||||
fn name() -> String {
|
||||
"string".to_string()
|
||||
}
|
||||
|
||||
fn decl() -> String {
|
||||
std::path::PathBuf::decl()
|
||||
}
|
||||
|
||||
fn decl_concrete() -> String {
|
||||
std::path::PathBuf::decl_concrete()
|
||||
}
|
||||
|
||||
fn inline() -> String {
|
||||
std::path::PathBuf::inline()
|
||||
}
|
||||
|
||||
fn inline_flattened() -> String {
|
||||
std::path::PathBuf::inline_flattened()
|
||||
}
|
||||
|
||||
fn output_path() -> Option<&'static std::path::Path> {
|
||||
std::path::PathBuf::output_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl schemars::JsonSchema for TypedPath {
|
||||
fn schema_name() -> String {
|
||||
"TypedPath".to_owned()
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
// TODO: Actually generate a reasonable schema.
|
||||
gen.subschema_for::<std::path::PathBuf>()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user