test the wasm side (#6726)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
40
.github/workflows/cargo-test.yml
vendored
40
.github/workflows/cargo-test.yml
vendored
@ -2,7 +2,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
permissions:
|
permissions:
|
||||||
@ -131,7 +130,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: nextest-archive
|
name: nextest-archive
|
||||||
path: rust/nextest-archive.tar.zst
|
path: rust/nextest-archive.tar.zst
|
||||||
|
|
||||||
run-test-artifacts:
|
run-test-artifacts:
|
||||||
name: cargo test (shard ${{ matrix.partitionIndex}})
|
name: cargo test (shard ${{ matrix.partitionIndex}})
|
||||||
runs-on:
|
runs-on:
|
||||||
@ -186,4 +184,40 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
||||||
ZOO_HOST: https://api.dev.zoo.dev
|
ZOO_HOST: https://api.dev.zoo.dev
|
||||||
|
run-wasm-tests:
|
||||||
|
name: Run wasm tests
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest]
|
||||||
|
fail-fast: false
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Use correct Rust toolchain
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
|
||||||
|
- name: Install rust
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
cache: false # Configured below.
|
||||||
|
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
|
||||||
|
with:
|
||||||
|
tool: wasm-pack
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: './rust'
|
||||||
|
- name: Build Wasm
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm run build:wasm
|
||||||
|
- name: Run wasm tests
|
||||||
|
run: |
|
||||||
|
cd rust
|
||||||
|
cd kcl-wasm-lib
|
||||||
|
#wasm-pack test --headless --chrome
|
||||||
|
env:
|
||||||
|
KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
|
NODE_ENV: development
|
||||||
|
@ -3,23 +3,18 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
|||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
|
|
||||||
test.describe('Import UI tests', () => {
|
test.describe('Import UI tests', () => {
|
||||||
test('shows toast when trying to sketch on imported face, and hovering over imported geometry should NOT highlight any code', async ({
|
test(
|
||||||
context,
|
'shows toast when trying to sketch on imported face, and hovering over imported geometry should NOT highlight any code',
|
||||||
page,
|
{ tag: ['@electron', '@macos', '@windows'] },
|
||||||
homePage,
|
async ({ context, page, homePage, toolbar, scene, editor, cmdBar }) => {
|
||||||
toolbar,
|
await context.folderSetupFn(async (dir) => {
|
||||||
scene,
|
const projectDir = path.join(dir, 'import-test')
|
||||||
editor,
|
await fsp.mkdir(projectDir, { recursive: true })
|
||||||
cmdBar,
|
|
||||||
}) => {
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
|
||||||
const projectDir = path.join(dir, 'import-test')
|
|
||||||
await fsp.mkdir(projectDir, { recursive: true })
|
|
||||||
|
|
||||||
// Create the imported file
|
// Create the imported file
|
||||||
await fsp.writeFile(
|
await fsp.writeFile(
|
||||||
path.join(projectDir, 'toBeImported.kcl'),
|
path.join(projectDir, 'toBeImported.kcl'),
|
||||||
`sketch001 = startSketchOn(XZ)
|
`sketch001 = startSketchOn(XZ)
|
||||||
profile001 = startProfile(sketch001, at = [281.54, 305.81])
|
profile001 = startProfile(sketch001, at = [281.54, 305.81])
|
||||||
|> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
|
|> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
|
||||||
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
|
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
|
||||||
@ -27,12 +22,12 @@ profile001 = startProfile(sketch001, at = [281.54, 305.81])
|
|||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
extrude(profile001, length = 100)`
|
extrude(profile001, length = 100)`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create the main file that imports
|
// Create the main file that imports
|
||||||
await fsp.writeFile(
|
await fsp.writeFile(
|
||||||
path.join(projectDir, 'main.kcl'),
|
path.join(projectDir, 'main.kcl'),
|
||||||
`import "toBeImported.kcl" as importedCube
|
`import "toBeImported.kcl" as importedCube
|
||||||
|
|
||||||
importedCube
|
importedCube
|
||||||
|
|
||||||
@ -46,63 +41,66 @@ profile001 = startProfile(sketch001, at = [-134.53, -56.17])
|
|||||||
extrude001 = extrude(profile001, length = 100)
|
extrude001 = extrude(profile001, length = 100)
|
||||||
sketch003 = startSketchOn(extrude001, face = seg02)
|
sketch003 = startSketchOn(extrude001, face = seg02)
|
||||||
sketch002 = startSketchOn(extrude001, face = seg01)`
|
sketch002 = startSketchOn(extrude001, face = seg01)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await homePage.openProject('import-test')
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
|
await scene.moveCameraTo(
|
||||||
|
{
|
||||||
|
x: -114,
|
||||||
|
y: -897,
|
||||||
|
z: 475,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: -114,
|
||||||
|
y: -51,
|
||||||
|
z: 83,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const [_, hoverOverNonImport] = scene.makeMouseHelpers(611, 364)
|
||||||
|
const [importedFaceClick, hoverOverImported] = scene.makeMouseHelpers(
|
||||||
|
940,
|
||||||
|
150
|
||||||
)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
await homePage.openProject('import-test')
|
await test.step('check code highlight works for code define in the file being edited', async () => {
|
||||||
await scene.settled(cmdBar)
|
await hoverOverNonImport()
|
||||||
|
await editor.expectState({
|
||||||
await scene.moveCameraTo(
|
highlightedCode: 'startProfile(sketch001,at = [-134.53,-56.17])',
|
||||||
{
|
diagnostics: [],
|
||||||
x: -114,
|
activeLines: ['import"toBeImported.kcl"asimportedCube'],
|
||||||
y: -897,
|
})
|
||||||
z: 475,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: -114,
|
|
||||||
y: -51,
|
|
||||||
z: 83,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const [_, hoverOverNonImport] = scene.makeMouseHelpers(611, 364)
|
|
||||||
const [importedFaceClick, hoverOverImported] = scene.makeMouseHelpers(
|
|
||||||
940,
|
|
||||||
150
|
|
||||||
)
|
|
||||||
|
|
||||||
await test.step('check code highlight works for code define in the file being edited', async () => {
|
|
||||||
await hoverOverNonImport()
|
|
||||||
await editor.expectState({
|
|
||||||
highlightedCode: 'startProfile(sketch001,at = [-134.53,-56.17])',
|
|
||||||
diagnostics: [],
|
|
||||||
activeLines: ['import"toBeImported.kcl"asimportedCube'],
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('check code does nothing when geometry is defined in an import', async () => {
|
await test.step('check code does nothing when geometry is defined in an import', async () => {
|
||||||
await hoverOverImported()
|
await hoverOverImported()
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
highlightedCode: '',
|
highlightedCode: '',
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
activeLines: ['import"toBeImported.kcl"asimportedCube'],
|
activeLines: ['import"toBeImported.kcl"asimportedCube'],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('check the user is warned when sketching on a imported face', async () => {
|
await test.step('check the user is warned when sketching on a imported face', async () => {
|
||||||
// Start sketch mode
|
// Start sketch mode
|
||||||
await toolbar.startSketchPlaneSelection()
|
await toolbar.startSketchPlaneSelection()
|
||||||
|
|
||||||
// Click on a face from the imported model
|
// Click on a face from the imported model
|
||||||
await importedFaceClick()
|
await importedFaceClick()
|
||||||
|
|
||||||
// Verify toast appears with correct content
|
// Verify toast appears with correct content
|
||||||
await expect(page.getByText('This face is from an import')).toBeVisible()
|
await expect(
|
||||||
await expect(
|
page.getByText('This face is from an import')
|
||||||
page.locator('.font-mono').getByText('toBeImported.kcl')
|
).toBeVisible()
|
||||||
).toBeVisible()
|
await expect(
|
||||||
await expect(
|
page.locator('.font-mono').getByText('toBeImported.kcl')
|
||||||
page.getByText('Please select this from the files pane to edit')
|
).toBeVisible()
|
||||||
).toBeVisible()
|
await expect(
|
||||||
})
|
page.getByText('Please select this from the files pane to edit')
|
||||||
})
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -43,7 +43,7 @@ async function insertPartIntoAssembly(
|
|||||||
test.describe('Point-and-click assemblies tests', () => {
|
test.describe('Point-and-click assemblies tests', () => {
|
||||||
test(
|
test(
|
||||||
`Insert kcl parts into assembly as whole module import`,
|
`Insert kcl parts into assembly as whole module import`,
|
||||||
{ tag: ['@electron'] },
|
{ tag: ['@electron', '@macos', '@windows'] },
|
||||||
async ({
|
async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -173,7 +173,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Can still translate, rotate, and delete inserted parts even with non standard code`,
|
`Can still translate, rotate, and delete inserted parts even with non standard code`,
|
||||||
{ tag: ['@electron'] },
|
{ tag: ['@electron', '@macos', '@windows'] },
|
||||||
async ({
|
async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -369,7 +369,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Insert the bracket part into an assembly and transform it`,
|
`Insert the bracket part into an assembly and transform it`,
|
||||||
{ tag: ['@electron'] },
|
{ tag: ['@electron', '@macos', '@windows'] },
|
||||||
async ({
|
async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -561,7 +561,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
// TODO: bring back in https://github.com/KittyCAD/modeling-app/issues/6570
|
// TODO: bring back in https://github.com/KittyCAD/modeling-app/issues/6570
|
||||||
test.fixme(
|
test.fixme(
|
||||||
`Insert foreign parts into assembly as whole module import`,
|
`Insert foreign parts into assembly as whole module import`,
|
||||||
{ tag: ['@electron'] },
|
{ tag: ['@electron', '@macos', '@windows'] },
|
||||||
async ({
|
async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -712,7 +712,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Assembly gets reexecuted when imported models are updated externally',
|
'Assembly gets reexecuted when imported models are updated externally',
|
||||||
{ tag: ['@electron'] },
|
{ tag: ['@electron', '@macos', '@windows'] },
|
||||||
async ({ context, page, homePage, scene, toolbar, cmdBar, tronApp }) => {
|
async ({ context, page, homePage, scene, toolbar, cmdBar, tronApp }) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
@ -802,7 +802,7 @@ foreign
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Point-and-click clone`,
|
`Point-and-click clone`,
|
||||||
{ tag: ['@electron'] },
|
{ tag: ['@electron', '@macos', '@windows'] },
|
||||||
async ({
|
async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
nodejs_22
|
nodejs_22
|
||||||
electron
|
electron
|
||||||
playwright-driver.browsers
|
playwright-driver.browsers
|
||||||
|
chromedriver
|
||||||
wasm-pack
|
wasm-pack
|
||||||
python3Full
|
python3Full
|
||||||
])
|
])
|
||||||
@ -71,12 +72,16 @@
|
|||||||
|
|
||||||
TARGET_CC = "${pkgs.stdenv.cc}/bin/${pkgs.stdenv.cc.targetPrefix}cc";
|
TARGET_CC = "${pkgs.stdenv.cc}/bin/${pkgs.stdenv.cc.targetPrefix}cc";
|
||||||
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
||||||
ELECTRON_OVERRIDE_DIST_PATH = if pkgs.stdenv.isDarwin then "${pkgs.electron}/Applications" else "${pkgs.electron}/bin";
|
ELECTRON_OVERRIDE_DIST_PATH =
|
||||||
|
if pkgs.stdenv.isDarwin
|
||||||
|
then "${pkgs.electron}/Applications"
|
||||||
|
else "${pkgs.electron}/bin";
|
||||||
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
|
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
|
||||||
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";
|
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";
|
||||||
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
|
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
|
||||||
NODE_ENV = "development";
|
NODE_ENV = "development";
|
||||||
RUSTFMT = "${pkgs.nightlyRustfmt}/bin/rustfmt";
|
RUSTFMT = "${pkgs.nightlyRustfmt}/bin/rustfmt";
|
||||||
|
CHROMEDRIVER = "${pkgs.chromedriver}/bin/chromedriver";
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
45
rust/Cargo.lock
generated
45
rust/Cargo.lock
generated
@ -1955,6 +1955,7 @@ dependencies = [
|
|||||||
"ts-rs",
|
"ts-rs",
|
||||||
"twenty-twenty",
|
"twenty-twenty",
|
||||||
"tynm",
|
"tynm",
|
||||||
|
"typed-path",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
"validator",
|
"validator",
|
||||||
@ -2013,6 +2014,7 @@ dependencies = [
|
|||||||
name = "kcl-wasm-lib"
|
name = "kcl-wasm-lib"
|
||||||
version = "0.1.66"
|
version = "0.1.66"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"bson",
|
"bson",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
@ -2024,13 +2026,16 @@ dependencies = [
|
|||||||
"kcl-lib",
|
"kcl-lib",
|
||||||
"kittycad",
|
"kittycad",
|
||||||
"kittycad-modeling-cmds",
|
"kittycad-modeling-cmds",
|
||||||
|
"pretty_assertions",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"tower-lsp",
|
"tower-lsp",
|
||||||
|
"typed-path",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
"wasm-bindgen-test",
|
||||||
"wasm-streams",
|
"wasm-streams",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
@ -2318,6 +2323,16 @@ dependencies = [
|
|||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minicov"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -4386,6 +4401,12 @@ dependencies = [
|
|||||||
"nom",
|
"nom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typed-path"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c462d18470a2857aa657d338af5fa67170bb48bcc80a296710ce3b0802a32566"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.18.0"
|
version = "1.18.0"
|
||||||
@ -4643,6 +4664,30 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-test"
|
||||||
|
version = "0.3.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"minicov",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"wasm-bindgen-test-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-test-macro"
|
||||||
|
version = "0.3.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-streams"
|
name = "wasm-streams"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
@ -96,6 +96,7 @@ instant = { version = "0.1.13", features = ["wasm-bindgen", "inaccurate"] }
|
|||||||
js-sys = { version = "0.3.72" }
|
js-sys = { version = "0.3.72" }
|
||||||
tokio = { workspace = true, features = ["sync", "time"] }
|
tokio = { workspace = true, features = ["sync", "time"] }
|
||||||
tower-lsp = { workspace = true, features = ["runtime-agnostic"] }
|
tower-lsp = { workspace = true, features = ["runtime-agnostic"] }
|
||||||
|
typed-path = "0.11.0"
|
||||||
wasm-bindgen = "0.2.99"
|
wasm-bindgen = "0.2.99"
|
||||||
wasm-bindgen-futures = "0.4.49"
|
wasm-bindgen-futures = "0.4.49"
|
||||||
wasm-timer = { package = "zduny-wasm-timer", version = "0.2.5" }
|
wasm-timer = { package = "zduny-wasm-timer", version = "0.2.5" }
|
||||||
|
@ -45,7 +45,7 @@ async fn cache_test(
|
|||||||
std::fs::write(tmp_file, variant_code).unwrap();
|
std::fs::write(tmp_file, variant_code).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.settings.project_directory = Some(tmp_dir.clone());
|
ctx.settings.project_directory = Some(kcl_lib::TypedPath(tmp_dir.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let outcome = match ctx.run_with_caching(program).await {
|
let outcome = match ctx.run_with_caching(program).await {
|
||||||
|
@ -22,6 +22,12 @@ extern "C" {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub type EngineCommandManager;
|
pub type EngineCommandManager;
|
||||||
|
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new() -> EngineCommandManager;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, js_name = startFromWasm, catch)]
|
||||||
|
pub async fn start_from_wasm(this: &EngineCommandManager, token: &str) -> Result<JsValue, js_sys::Error>;
|
||||||
|
|
||||||
#[wasm_bindgen(method, js_name = fireModelingCommandFromWasm, catch)]
|
#[wasm_bindgen(method, js_name = fireModelingCommandFromWasm, catch)]
|
||||||
fn fire_modeling_cmd_from_wasm(
|
fn fire_modeling_cmd_from_wasm(
|
||||||
this: &EngineCommandManager,
|
this: &EngineCommandManager,
|
||||||
|
@ -676,7 +676,9 @@ extrude(profile001, length = 100)"#
|
|||||||
std::fs::write(tmp_file, other_file.1).unwrap();
|
std::fs::write(tmp_file, other_file.1).unwrap();
|
||||||
|
|
||||||
let ExecTestResults { program, exec_ctxt, .. } =
|
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();
|
let mut new_program = crate::Program::parse_no_errs(code).unwrap();
|
||||||
new_program.compute_digest();
|
new_program.compute_digest();
|
||||||
@ -755,7 +757,9 @@ extrude(profile001, length = 100)
|
|||||||
std::fs::write(&tmp_file, other_file.1).unwrap();
|
std::fs::write(&tmp_file, other_file.1).unwrap();
|
||||||
|
|
||||||
let ExecTestResults { program, exec_ctxt, .. } =
|
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.
|
// Change the other file.
|
||||||
std::fs::write(tmp_file, other_file2.1).unwrap();
|
std::fs::write(tmp_file, other_file2.1).unwrap();
|
||||||
|
@ -2643,7 +2643,7 @@ d = b + c
|
|||||||
)),
|
)),
|
||||||
fs: Arc::new(crate::fs::FileManager::new()),
|
fs: Arc::new(crate::fs::FileManager::new()),
|
||||||
settings: ExecutorSettings {
|
settings: ExecutorSettings {
|
||||||
project_directory: Some(tmpdir.path().into()),
|
project_directory: Some(crate::TypedPath(tmpdir.path().into())),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
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 anyhow::Result;
|
||||||
use kcmc::{
|
use kcmc::{
|
||||||
@ -15,7 +15,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{annotations, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry},
|
execution::{annotations, typed_path::TypedPath, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry},
|
||||||
fs::FileSystem,
|
fs::FileSystem,
|
||||||
parsing::ast::types::{Annotation, Node},
|
parsing::ast::types::{Annotation, Node},
|
||||||
source_range::SourceRange,
|
source_range::SourceRange,
|
||||||
@ -29,7 +29,7 @@ use crate::{
|
|||||||
pub const ZOO_COORD_SYSTEM: System = *KITTYCAD;
|
pub const ZOO_COORD_SYSTEM: System = *KITTYCAD;
|
||||||
|
|
||||||
pub async fn import_foreign(
|
pub async fn import_foreign(
|
||||||
file_path: &Path,
|
file_path: &TypedPath,
|
||||||
format: Option<InputFormat3d>,
|
format: Option<InputFormat3d>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
ctxt: &ExecutorContext,
|
ctxt: &ExecutorContext,
|
||||||
@ -43,19 +43,18 @@ pub async fn import_foreign(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ext_format =
|
let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| {
|
||||||
get_import_format_from_extension(file_path.extension().and_then(OsStr::to_str).ok_or_else(|| {
|
KclError::Semantic(KclErrorDetails {
|
||||||
KclError::Semantic(KclErrorDetails {
|
message: format!("No file extension found for `{}`", file_path.display()),
|
||||||
message: format!("No file extension found for `{}`", file_path.display()),
|
source_ranges: vec![source_range],
|
||||||
source_ranges: vec![source_range],
|
})
|
||||||
})
|
})?)
|
||||||
})?)
|
.map_err(|e| {
|
||||||
.map_err(|e| {
|
KclError::Semantic(KclErrorDetails {
|
||||||
KclError::Semantic(KclErrorDetails {
|
message: e.to_string(),
|
||||||
message: e.to_string(),
|
source_ranges: vec![source_range],
|
||||||
source_ranges: vec![source_range],
|
})
|
||||||
})
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
// Get the format type from the extension of the file.
|
// Get the format type from the extension of the file.
|
||||||
let format = if let Some(format) = format {
|
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.
|
// We want the file_path to be without the parent.
|
||||||
let file_name = std::path::Path::new(&file_path)
|
let file_name = file_path.file_name().map(|p| p.to_string()).ok_or_else(|| {
|
||||||
.file_name()
|
KclError::Semantic(KclErrorDetails {
|
||||||
.map(|p| p.to_string_lossy().to_string())
|
message: format!("Could not get the file name from the path `{}`", file_path.display()),
|
||||||
.ok_or_else(|| {
|
source_ranges: vec![source_range],
|
||||||
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 {
|
let mut import_files = vec![kcmc::ImportFile {
|
||||||
path: file_name.to_string(),
|
path: file_name.to_string(),
|
||||||
data: file_contents.clone(),
|
data: file_contents.clone(),
|
||||||
@ -112,19 +108,12 @@ pub async fn import_foreign(
|
|||||||
if let Some(uri) = &buffer.uri {
|
if let Some(uri) = &buffer.uri {
|
||||||
if !uri.starts_with("data:") {
|
if !uri.starts_with("data:") {
|
||||||
// We want this path relative to the file_path given.
|
// We want this path relative to the file_path given.
|
||||||
let bin_path = std::path::Path::new(&file_path)
|
let bin_path = file_path.parent().map(|p| p.join(uri)).ok_or_else(|| {
|
||||||
.parent()
|
KclError::Semantic(KclErrorDetails {
|
||||||
.map(|p| p.join(uri))
|
message: format!("Could not get the parent path of the file `{}`", file_path.display()),
|
||||||
.map(|p| p.to_string_lossy().to_string())
|
source_ranges: vec![source_range],
|
||||||
.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| {
|
let bin_contents = ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
|
||||||
KclError::Semantic(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
@ -154,7 +143,7 @@ pub async fn import_foreign(
|
|||||||
|
|
||||||
pub(super) fn format_from_annotations(
|
pub(super) fn format_from_annotations(
|
||||||
annotations: &[Node<Annotation>],
|
annotations: &[Node<Annotation>],
|
||||||
path: &Path,
|
path: &TypedPath,
|
||||||
import_source_range: SourceRange,
|
import_source_range: SourceRange,
|
||||||
) -> Result<Option<InputFormat3d>, KclError> {
|
) -> Result<Option<InputFormat3d>, KclError> {
|
||||||
if annotations.is_empty() {
|
if annotations.is_empty() {
|
||||||
@ -184,7 +173,6 @@ pub(super) fn format_from_annotations(
|
|||||||
let mut result = result
|
let mut result = result
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
path.extension()
|
path.extension()
|
||||||
.and_then(OsStr::to_str)
|
|
||||||
.and_then(|ext| get_import_format_from_extension(ext).ok())
|
.and_then(|ext| get_import_format_from_extension(ext).ok())
|
||||||
})
|
})
|
||||||
.ok_or(KclError::Semantic(KclErrorDetails {
|
.ok_or(KclError::Semantic(KclErrorDetails {
|
||||||
@ -397,7 +385,7 @@ mod test {
|
|||||||
fn annotations() {
|
fn annotations() {
|
||||||
// no annotations
|
// no annotations
|
||||||
assert!(
|
assert!(
|
||||||
format_from_annotations(&[], Path::new("../foo.txt"), SourceRange::default(),)
|
format_from_annotations(&[], &TypedPath::from("../foo.txt"), SourceRange::default(),)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_none()
|
.is_none()
|
||||||
);
|
);
|
||||||
@ -406,7 +394,7 @@ mod test {
|
|||||||
let text = "@()\nimport '../foo.gltf' as foo";
|
let text = "@()\nimport '../foo.gltf' as foo";
|
||||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||||
let attrs = parsed.body[0].get_attrs();
|
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()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -418,7 +406,7 @@ mod test {
|
|||||||
let text = "@(format = gltf)\nimport '../foo.txt' as foo";
|
let text = "@(format = gltf)\nimport '../foo.txt' as foo";
|
||||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||||
let attrs = parsed.body[0].get_attrs();
|
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()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -427,7 +415,7 @@ mod test {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// format, no extension (wouldn't parse but might some day)
|
// 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()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -439,7 +427,7 @@ mod test {
|
|||||||
let text = "@(format = obj, coords = vulkan, lengthUnit = ft)\nimport '../foo.txt' as foo";
|
let text = "@(format = obj, coords = vulkan, lengthUnit = ft)\nimport '../foo.txt' as foo";
|
||||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||||
let attrs = parsed.body[0].get_attrs();
|
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()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -454,7 +442,7 @@ mod test {
|
|||||||
let text = "@(coords = vulkan, lengthUnit = ft)\nimport '../foo.obj' as foo";
|
let text = "@(coords = vulkan, lengthUnit = ft)\nimport '../foo.obj' as foo";
|
||||||
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
let parsed = crate::Program::parse_no_errs(text).unwrap().ast;
|
||||||
let attrs = parsed.body[0].get_attrs();
|
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()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -507,7 +495,7 @@ mod test {
|
|||||||
fn assert_annotation_error(src: &str, path: &str, expected: &str) {
|
fn assert_annotation_error(src: &str, path: &str, expected: &str) {
|
||||||
let parsed = crate::Program::parse_no_errs(src).unwrap().ast;
|
let parsed = crate::Program::parse_no_errs(src).unwrap().ast;
|
||||||
let attrs = parsed.body[0].get_attrs();
|
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!(
|
assert!(
|
||||||
err.message().contains(expected),
|
err.message().contains(expected),
|
||||||
"Expected: `{expected}`, found `{}`",
|
"Expected: `{expected}`, found `{}`",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! The executor for the AST.
|
//! The executor for the AST.
|
||||||
|
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
#[cfg(feature = "artifact-graph")]
|
#[cfg(feature = "artifact-graph")]
|
||||||
@ -35,6 +35,7 @@ use crate::{
|
|||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
cache::{CacheInformation, CacheResult},
|
cache::{CacheInformation, CacheResult},
|
||||||
|
typed_path::TypedPath,
|
||||||
types::{UnitAngle, UnitLen},
|
types::{UnitAngle, UnitLen},
|
||||||
},
|
},
|
||||||
fs::FileManager,
|
fs::FileManager,
|
||||||
@ -59,6 +60,7 @@ mod import;
|
|||||||
pub(crate) mod kcl_value;
|
pub(crate) mod kcl_value;
|
||||||
mod memory;
|
mod memory;
|
||||||
mod state;
|
mod state;
|
||||||
|
pub mod typed_path;
|
||||||
pub(crate) mod types;
|
pub(crate) mod types;
|
||||||
|
|
||||||
/// Outcome of executing a program. This is used in TS.
|
/// Outcome of executing a program. This is used in TS.
|
||||||
@ -287,10 +289,10 @@ pub struct ExecutorSettings {
|
|||||||
pub replay: Option<String>,
|
pub replay: Option<String>,
|
||||||
/// The directory of the current project. This is used for resolving import
|
/// The directory of the current project. This is used for resolving import
|
||||||
/// paths. If None is given, the current working directory is used.
|
/// 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.
|
/// This is the path to the current file being executed.
|
||||||
/// We use this for preventing cyclic imports.
|
/// We use this for preventing cyclic imports.
|
||||||
pub current_file: Option<PathBuf>,
|
pub current_file: Option<TypedPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExecutorSettings {
|
impl Default for ExecutorSettings {
|
||||||
@ -360,15 +362,15 @@ impl From<crate::settings::types::project::ProjectModelingSettings> for Executor
|
|||||||
|
|
||||||
impl ExecutorSettings {
|
impl ExecutorSettings {
|
||||||
/// Add the current file path to the executor settings.
|
/// 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.
|
// 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());
|
self.current_file = Some(current_file.clone());
|
||||||
// Get the parent directory.
|
// Get the parent directory.
|
||||||
if let Some(parent) = current_file.parent() {
|
if let Some(parent) = current_file.parent() {
|
||||||
self.project_directory = Some(parent.to_path_buf());
|
self.project_directory = Some(parent);
|
||||||
} else {
|
} else {
|
||||||
self.project_directory = Some(std::path::PathBuf::from(""));
|
self.project_directory = Some(TypedPath::from(""));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.project_directory = Some(current_file.clone());
|
self.project_directory = Some(current_file.clone());
|
||||||
@ -856,7 +858,7 @@ impl ExecutorContext {
|
|||||||
if universe_map.contains_key(value) {
|
if universe_map.contains_key(value) {
|
||||||
exec_state.global.operations.push(Operation::GroupBegin {
|
exec_state.global.operations.push(Operation::GroupBegin {
|
||||||
group: Group::ModuleInstance {
|
group: Group::ModuleInstance {
|
||||||
name: value.file_name().unwrap_or_default().to_string_lossy().into_owned(),
|
name: value.file_name().unwrap_or_default(),
|
||||||
module_id,
|
module_id,
|
||||||
},
|
},
|
||||||
source_range,
|
source_range,
|
||||||
@ -1306,7 +1308,7 @@ pub(crate) async fn parse_execute(code: &str) -> Result<ExecTestResults, KclErro
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) async fn parse_execute_with_project_dir(
|
pub(crate) async fn parse_execute_with_project_dir(
|
||||||
code: &str,
|
code: &str,
|
||||||
project_directory: Option<std::path::PathBuf>,
|
project_directory: Option<TypedPath>,
|
||||||
) -> Result<ExecTestResults, KclError> {
|
) -> Result<ExecTestResults, KclError> {
|
||||||
let program = crate::Program::parse_no_errs(code)?;
|
let program = crate::Program::parse_no_errs(code)?;
|
||||||
|
|
||||||
|
@ -275,7 +275,7 @@ impl ExecState {
|
|||||||
.mod_loader
|
.mod_loader
|
||||||
.import_stack
|
.import_stack
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| p.as_path().to_string_lossy())
|
.map(|p| p.to_string_lossy())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" -> "),
|
.join(" -> "),
|
||||||
path,
|
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>()
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::execution::typed_path::TypedPath;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
fs::FileSystem,
|
fs::FileSystem,
|
||||||
@ -25,56 +26,44 @@ impl Default for FileManager {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl FileSystem for FileManager {
|
impl FileSystem for FileManager {
|
||||||
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> {
|
||||||
&self,
|
tokio::fs::read(&path.0).await.map_err(|e| {
|
||||||
path: P,
|
|
||||||
source_range: SourceRange,
|
|
||||||
) -> Result<Vec<u8>, KclError> {
|
|
||||||
tokio::fs::read(&path).await.map_err(|e| {
|
|
||||||
KclError::Io(KclErrorDetails {
|
KclError::Io(KclErrorDetails {
|
||||||
message: format!("Failed to read file `{}`: {}", path.as_ref().display(), e),
|
message: format!("Failed to read file `{}`: {}", path.display(), e),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> {
|
||||||
&self,
|
tokio::fs::read_to_string(&path.0).await.map_err(|e| {
|
||||||
path: P,
|
|
||||||
source_range: SourceRange,
|
|
||||||
) -> Result<String, KclError> {
|
|
||||||
tokio::fs::read_to_string(&path).await.map_err(|e| {
|
|
||||||
KclError::Io(KclErrorDetails {
|
KclError::Io(KclErrorDetails {
|
||||||
message: format!("Failed to read file `{}`: {}", path.as_ref().display(), e),
|
message: format!("Failed to read file `{}`: {}", path.display(), e),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn exists(&self, path: &TypedPath, source_range: SourceRange) -> Result<bool, crate::errors::KclError> {
|
||||||
&self,
|
tokio::fs::metadata(&path.0).await.map(|_| true).or_else(|e| {
|
||||||
path: P,
|
|
||||||
source_range: SourceRange,
|
|
||||||
) -> Result<bool, crate::errors::KclError> {
|
|
||||||
tokio::fs::metadata(&path).await.map(|_| true).or_else(|e| {
|
|
||||||
if e.kind() == std::io::ErrorKind::NotFound {
|
if e.kind() == std::io::ErrorKind::NotFound {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
} else {
|
} else {
|
||||||
Err(KclError::Io(KclErrorDetails {
|
Err(KclError::Io(KclErrorDetails {
|
||||||
message: format!("Failed to check if file `{}` exists: {}", path.as_ref().display(), e),
|
message: format!("Failed to check if file `{}` exists: {}", path.display(), e),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn get_all_files(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: &TypedPath,
|
||||||
source_range: SourceRange,
|
source_range: SourceRange,
|
||||||
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> {
|
) -> Result<Vec<TypedPath>, crate::errors::KclError> {
|
||||||
let mut files = vec![];
|
let mut files = vec![];
|
||||||
let mut stack = vec![path.as_ref().to_path_buf()];
|
let mut stack = vec![path.0.to_path_buf()];
|
||||||
|
|
||||||
while let Some(path) = stack.pop() {
|
while let Some(path) = stack.pop() {
|
||||||
if !path.is_dir() {
|
if !path.is_dir() {
|
||||||
@ -94,7 +83,7 @@ impl FileSystem for FileManager {
|
|||||||
// Iterate over the directory.
|
// Iterate over the directory.
|
||||||
stack.push(path);
|
stack.push(path);
|
||||||
} else {
|
} else {
|
||||||
files.push(path);
|
files.push(TypedPath(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::execution::typed_path::TypedPath;
|
||||||
use crate::SourceRange;
|
use crate::SourceRange;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@ -20,30 +21,22 @@ pub use wasm::FileManager;
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait FileSystem: Clone {
|
pub trait FileSystem: Clone {
|
||||||
/// Read a file from the local file system.
|
/// Read a file from the local file system.
|
||||||
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, crate::errors::KclError>;
|
||||||
&self,
|
|
||||||
path: P,
|
|
||||||
source_range: SourceRange,
|
|
||||||
) -> Result<Vec<u8>, crate::errors::KclError>;
|
|
||||||
|
|
||||||
/// Read a file from the local file system.
|
/// Read a file from the local file system.
|
||||||
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn read_to_string(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: &TypedPath,
|
||||||
source_range: SourceRange,
|
source_range: SourceRange,
|
||||||
) -> Result<String, crate::errors::KclError>;
|
) -> Result<String, crate::errors::KclError>;
|
||||||
|
|
||||||
/// Check if a file exists on the local file system.
|
/// Check if a file exists on the local file system.
|
||||||
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn exists(&self, path: &TypedPath, source_range: SourceRange) -> Result<bool, crate::errors::KclError>;
|
||||||
&self,
|
|
||||||
path: P,
|
|
||||||
source_range: SourceRange,
|
|
||||||
) -> Result<bool, crate::errors::KclError>;
|
|
||||||
|
|
||||||
/// Get all the files in a directory recursively.
|
/// Get all the files in a directory recursively.
|
||||||
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn get_all_files(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: &TypedPath,
|
||||||
source_range: SourceRange,
|
source_range: SourceRange,
|
||||||
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError>;
|
) -> Result<Vec<TypedPath>, crate::errors::KclError>;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ use wasm_bindgen::prelude::wasm_bindgen;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
|
execution::typed_path::TypedPath,
|
||||||
fs::FileSystem,
|
fs::FileSystem,
|
||||||
wasm::JsFuture,
|
wasm::JsFuture,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
@ -15,6 +16,9 @@ extern "C" {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub type FileSystemManager;
|
pub type FileSystemManager;
|
||||||
|
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new() -> FileSystemManager;
|
||||||
|
|
||||||
#[wasm_bindgen(method, js_name = readFile, catch)]
|
#[wasm_bindgen(method, js_name = readFile, catch)]
|
||||||
fn read_file(this: &FileSystemManager, path: String) -> Result<js_sys::Promise, js_sys::Error>;
|
fn read_file(this: &FileSystemManager, path: String) -> Result<js_sys::Promise, js_sys::Error>;
|
||||||
|
|
||||||
@ -41,30 +45,13 @@ unsafe impl Sync for FileManager {}
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl FileSystem for FileManager {
|
impl FileSystem for FileManager {
|
||||||
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> {
|
||||||
&self,
|
let promise = self.manager.read_file(path.to_string_lossy()).map_err(|e| {
|
||||||
path: P,
|
KclError::Engine(KclErrorDetails {
|
||||||
source_range: SourceRange,
|
message: e.to_string().into(),
|
||||||
) -> Result<Vec<u8>, KclError> {
|
source_ranges: vec![source_range],
|
||||||
let promise = self
|
})
|
||||||
.manager
|
})?;
|
||||||
.read_file(
|
|
||||||
path.as_ref()
|
|
||||||
.to_str()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
KclError::Engine(KclErrorDetails {
|
|
||||||
message: "Failed to convert path to string".to_string(),
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
KclError::Engine(KclErrorDetails {
|
|
||||||
message: e.to_string().into(),
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let value = JsFuture::from(promise).await.map_err(|e| {
|
let value = JsFuture::from(promise).await.map_err(|e| {
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
@ -79,11 +66,7 @@ impl FileSystem for FileManager {
|
|||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> {
|
||||||
&self,
|
|
||||||
path: P,
|
|
||||||
source_range: SourceRange,
|
|
||||||
) -> Result<String, KclError> {
|
|
||||||
let bytes = self.read(path, source_range).await?;
|
let bytes = self.read(path, source_range).await?;
|
||||||
let string = String::from_utf8(bytes).map_err(|e| {
|
let string = String::from_utf8(bytes).map_err(|e| {
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
@ -95,30 +78,13 @@ impl FileSystem for FileManager {
|
|||||||
Ok(string)
|
Ok(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn exists(&self, path: &TypedPath, source_range: SourceRange) -> Result<bool, crate::errors::KclError> {
|
||||||
&self,
|
let promise = self.manager.exists(path.to_string_lossy()).map_err(|e| {
|
||||||
path: P,
|
KclError::Engine(KclErrorDetails {
|
||||||
source_range: SourceRange,
|
message: e.to_string().into(),
|
||||||
) -> Result<bool, crate::errors::KclError> {
|
source_ranges: vec![source_range],
|
||||||
let promise = self
|
})
|
||||||
.manager
|
})?;
|
||||||
.exists(
|
|
||||||
path.as_ref()
|
|
||||||
.to_str()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
KclError::Engine(KclErrorDetails {
|
|
||||||
message: "Failed to convert path to string".to_string(),
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
KclError::Engine(KclErrorDetails {
|
|
||||||
message: e.to_string().into(),
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let value = JsFuture::from(promise).await.map_err(|e| {
|
let value = JsFuture::from(promise).await.map_err(|e| {
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
@ -137,30 +103,17 @@ impl FileSystem for FileManager {
|
|||||||
Ok(it_exists)
|
Ok(it_exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
async fn get_all_files(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: &TypedPath,
|
||||||
source_range: SourceRange,
|
source_range: SourceRange,
|
||||||
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> {
|
) -> Result<Vec<TypedPath>, crate::errors::KclError> {
|
||||||
let promise = self
|
let promise = self.manager.get_all_files(path.to_string_lossy()).map_err(|e| {
|
||||||
.manager
|
KclError::Engine(KclErrorDetails {
|
||||||
.get_all_files(
|
message: e.to_string().into(),
|
||||||
path.as_ref()
|
source_ranges: vec![source_range],
|
||||||
.to_str()
|
})
|
||||||
.ok_or_else(|| {
|
})?;
|
||||||
KclError::Engine(KclErrorDetails {
|
|
||||||
message: "Failed to convert path to string".to_string(),
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
KclError::Engine(KclErrorDetails {
|
|
||||||
message: e.to_string().into(),
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let value = JsFuture::from(promise).await.map_err(|e| {
|
let value = JsFuture::from(promise).await.map_err(|e| {
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
@ -183,6 +136,6 @@ impl FileSystem for FileManager {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(files.into_iter().map(std::path::PathBuf::from).collect())
|
Ok(files.into_iter().map(|s| TypedPath::from(&s)).collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,7 @@ pub use errors::{
|
|||||||
};
|
};
|
||||||
pub use execution::{
|
pub use execution::{
|
||||||
bust_cache, clear_mem_cache,
|
bust_cache, clear_mem_cache,
|
||||||
|
typed_path::TypedPath,
|
||||||
types::{UnitAngle, UnitLen},
|
types::{UnitAngle, UnitLen},
|
||||||
ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d,
|
ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d,
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ use tower_lsp::lsp_types::{
|
|||||||
TextDocumentItem, WorkspaceFolder,
|
TextDocumentItem, WorkspaceFolder,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::execution::typed_path::TypedPath;
|
||||||
use crate::fs::FileSystem;
|
use crate::fs::FileSystem;
|
||||||
|
|
||||||
/// A trait for the backend of the language server.
|
/// A trait for the backend of the language server.
|
||||||
@ -75,18 +76,13 @@ where
|
|||||||
self.inner_on_change(params, false).await;
|
self.inner_on_change(params, false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_from_disk<P: AsRef<std::path::Path> + std::marker::Send>(&self, path: P) -> Result<()> {
|
async fn update_from_disk(&self, path: &TypedPath) -> Result<()> {
|
||||||
// Read over all the files in the directory and add them to our current code map.
|
// Read over all the files in the directory and add them to our current code map.
|
||||||
let files = self.fs().get_all_files(path.as_ref(), Default::default()).await?;
|
let files = self.fs().get_all_files(path, Default::default()).await?;
|
||||||
for file in files {
|
for file in files {
|
||||||
// Read the file.
|
// Read the file.
|
||||||
let contents = self.fs().read(&file, Default::default()).await?;
|
let contents = self.fs().read(&file, Default::default()).await?;
|
||||||
let file_path = format!(
|
let file_path = format!("file://{}", file.to_string_lossy());
|
||||||
"file://{}",
|
|
||||||
file.as_path()
|
|
||||||
.to_str()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("could not get name of file: {:?}", file))?
|
|
||||||
);
|
|
||||||
self.insert_code_map(file_path, contents).await;
|
self.insert_code_map(file_path, contents).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +135,7 @@ where
|
|||||||
}
|
}
|
||||||
for added in params.event.added {
|
for added in params.event.added {
|
||||||
// Try to read all the files in the project.
|
// Try to read all the files in the project.
|
||||||
let project_dir = added.uri.to_string().replace("file://", "");
|
let project_dir = TypedPath::from(&added.uri.to_string().replace("file://", ""));
|
||||||
if let Err(err) = self.update_from_disk(&project_dir).await {
|
if let Err(err) = self.update_from_disk(&project_dir).await {
|
||||||
self.client()
|
self.client()
|
||||||
.log_message(
|
.log_message(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{fmt, path::PathBuf};
|
use std::fmt;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
exec::KclValue,
|
exec::KclValue,
|
||||||
execution::{EnvironmentRef, PreImportedGeometry},
|
execution::{typed_path::TypedPath, EnvironmentRef, PreImportedGeometry},
|
||||||
fs::{FileManager, FileSystem},
|
fs::{FileManager, FileSystem},
|
||||||
parsing::ast::types::{ImportPath, Node, Program},
|
parsing::ast::types::{ImportPath, Node, Program},
|
||||||
source_range::SourceRange,
|
source_range::SourceRange,
|
||||||
@ -46,7 +46,7 @@ impl std::fmt::Display for ModuleId {
|
|||||||
pub(crate) struct ModuleLoader {
|
pub(crate) struct ModuleLoader {
|
||||||
/// The stack of import statements for detecting circular module imports.
|
/// The stack of import statements for detecting circular module imports.
|
||||||
/// If this is empty, we're not currently executing an import statement.
|
/// If this is empty, we're not currently executing an import statement.
|
||||||
pub import_stack: Vec<PathBuf>,
|
pub import_stack: Vec<TypedPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModuleLoader {
|
impl ModuleLoader {
|
||||||
@ -63,7 +63,7 @@ impl ModuleLoader {
|
|||||||
"circular import of modules is not allowed: {} -> {}",
|
"circular import of modules is not allowed: {} -> {}",
|
||||||
self.import_stack
|
self.import_stack
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| p.as_path().to_string_lossy())
|
.map(|p| p.to_string_lossy())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" -> "),
|
.join(" -> "),
|
||||||
path,
|
path,
|
||||||
@ -140,12 +140,12 @@ pub enum ModuleRepr {
|
|||||||
pub enum ModulePath {
|
pub enum ModulePath {
|
||||||
// The main file of the project.
|
// The main file of the project.
|
||||||
Main,
|
Main,
|
||||||
Local { value: PathBuf },
|
Local { value: TypedPath },
|
||||||
Std { value: String },
|
Std { value: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModulePath {
|
impl ModulePath {
|
||||||
pub(crate) fn expect_path(&self) -> &PathBuf {
|
pub(crate) fn expect_path(&self) -> &TypedPath {
|
||||||
match self {
|
match self {
|
||||||
ModulePath::Local { value: p } => p,
|
ModulePath::Local { value: p } => p,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@ -180,13 +180,13 @@ impl ModulePath {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_import_path(path: &ImportPath, project_directory: &Option<PathBuf>) -> Self {
|
pub(crate) fn from_import_path(path: &ImportPath, project_directory: &Option<TypedPath>) -> Self {
|
||||||
match path {
|
match path {
|
||||||
ImportPath::Kcl { filename: path } | ImportPath::Foreign { path } => {
|
ImportPath::Kcl { filename: path } | ImportPath::Foreign { path } => {
|
||||||
let resolved_path = if let Some(project_dir) = project_directory {
|
let resolved_path = if let Some(project_dir) = project_directory {
|
||||||
project_dir.join(path)
|
project_dir.join(path)
|
||||||
} else {
|
} else {
|
||||||
std::path::PathBuf::from(path)
|
TypedPath::from(path)
|
||||||
};
|
};
|
||||||
ModulePath::Local { value: resolved_path }
|
ModulePath::Local { value: resolved_path }
|
||||||
}
|
}
|
||||||
@ -205,7 +205,7 @@ impl fmt::Display for ModulePath {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ModulePath::Main => write!(f, "main"),
|
ModulePath::Main => write!(f, "main"),
|
||||||
ModulePath::Local { value: path } => path.display().fmt(f),
|
ModulePath::Local { value: path } => path.fmt(f),
|
||||||
ModulePath::Std { value: s } => write!(f, "std::{s}"),
|
ModulePath::Std { value: s } => write!(f, "std::{s}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ pub async fn new_context(with_auth: bool, current_file: Option<PathBuf>) -> Resu
|
|||||||
current_file: None,
|
current_file: None,
|
||||||
};
|
};
|
||||||
if let Some(current_file) = current_file {
|
if let Some(current_file) = current_file {
|
||||||
settings.with_current_file(current_file);
|
settings.with_current_file(crate::TypedPath(current_file));
|
||||||
}
|
}
|
||||||
let ctx = ExecutorContext::new(&client, settings)
|
let ctx = ExecutorContext::new(&client, settings)
|
||||||
.await
|
.await
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::PathBuf,
|
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -8,6 +7,7 @@ use anyhow::Result;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::KclErrorDetails,
|
errors::KclErrorDetails,
|
||||||
|
execution::typed_path::TypedPath,
|
||||||
modules::{ModulePath, ModuleRepr},
|
modules::{ModulePath, ModuleRepr},
|
||||||
parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode},
|
parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode},
|
||||||
walk::{Node, Visitable},
|
walk::{Node, Visitable},
|
||||||
@ -22,7 +22,7 @@ type Dependency = (String, String);
|
|||||||
type Graph = Vec<Dependency>;
|
type Graph = Vec<Dependency>;
|
||||||
|
|
||||||
pub(crate) type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, ModuleRepr);
|
pub(crate) type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, ModuleRepr);
|
||||||
pub(crate) type UniverseMap = HashMap<PathBuf, AstNode<ImportStatement>>;
|
pub(crate) type UniverseMap = HashMap<TypedPath, AstNode<ImportStatement>>;
|
||||||
pub(crate) type Universe = HashMap<String, DependencyInfo>;
|
pub(crate) type Universe = HashMap<String, DependencyInfo>;
|
||||||
|
|
||||||
/// Process a number of programs, returning the graph of dependencies.
|
/// Process a number of programs, returning the graph of dependencies.
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
//! Web assembly utils.
|
//! Web assembly utils.
|
||||||
|
|
||||||
pub mod vitest;
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
use js_sys::Reflect;
|
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
|
|
||||||
/// returns true if globalThis.process?.env?.VITEST is truthy
|
|
||||||
fn is_vitest_by_env() -> bool {
|
|
||||||
let global = js_sys::global();
|
|
||||||
|
|
||||||
// global.process
|
|
||||||
let process = Reflect::get(&global, &JsValue::from_str("process"))
|
|
||||||
.ok()
|
|
||||||
.unwrap_or_else(|| JsValue::NULL);
|
|
||||||
// process.env
|
|
||||||
let env = Reflect::get(&process, &JsValue::from_str("env"))
|
|
||||||
.ok()
|
|
||||||
.unwrap_or_else(|| JsValue::NULL);
|
|
||||||
// env.VITEST
|
|
||||||
let vitest = Reflect::get(&env, &JsValue::from_str("VITEST"))
|
|
||||||
.ok()
|
|
||||||
.unwrap_or_else(|| JsValue::NULL);
|
|
||||||
|
|
||||||
// "true", "1", or a boolean
|
|
||||||
vitest
|
|
||||||
.as_bool()
|
|
||||||
.unwrap_or_else(|| vitest.as_string().map_or(false, |s| s == "true" || s == "1"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_vitest_by_global() -> bool {
|
|
||||||
let global = js_sys::global();
|
|
||||||
Reflect::has(&global, &JsValue::from_str("__vitest_worker__")).unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn running_in_vitest() -> bool {
|
|
||||||
let running_in_vitest = is_vitest_by_env() || is_vitest_by_global();
|
|
||||||
|
|
||||||
if running_in_vitest {
|
|
||||||
web_sys::console::log_1(&JsValue::from_str(&format!(
|
|
||||||
"running_in_vitest: {}, SOME BEHAVIOR MIGHT BE DIFFERENT THAN THE WASM IN THE APP",
|
|
||||||
running_in_vitest
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
running_in_vitest
|
|
||||||
}
|
|
@ -220,7 +220,7 @@ async fn get_code_and_file_path(path: &str) -> Result<(String, std::path::PathBu
|
|||||||
async fn new_context_state(current_file: Option<std::path::PathBuf>) -> Result<(ExecutorContext, kcl_lib::ExecState)> {
|
async fn new_context_state(current_file: Option<std::path::PathBuf>) -> Result<(ExecutorContext, kcl_lib::ExecState)> {
|
||||||
let mut settings: kcl_lib::ExecutorSettings = Default::default();
|
let mut settings: kcl_lib::ExecutorSettings = Default::default();
|
||||||
if let Some(current_file) = current_file {
|
if let Some(current_file) = current_file {
|
||||||
settings.with_current_file(current_file);
|
settings.with_current_file(kcl_lib::TypedPath(current_file));
|
||||||
}
|
}
|
||||||
let ctx = ExecutorContext::new_with_client(settings, None, None).await?;
|
let ctx = ExecutorContext::new_with_client(settings, None, None).await?;
|
||||||
let state = kcl_lib::ExecState::new(&ctx);
|
let state = kcl_lib::ExecState::new(&ctx);
|
||||||
|
@ -44,6 +44,14 @@ features = [
|
|||||||
[features]
|
[features]
|
||||||
dhat-heap = ["kcl-lib/dhat-heap"]
|
dhat-heap = ["kcl-lib/dhat-heap"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
pretty_assertions = "1.4.1"
|
||||||
|
wasm-bindgen-test = "0.3.50"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
typed-path = "0.11.0"
|
||||||
|
|
||||||
# Local development only. Placeholder to speed up development cycle
|
# Local development only. Placeholder to speed up development cycle
|
||||||
#[package.metadata.wasm-pack.profile.release]
|
#[package.metadata.wasm-pack.profile.release]
|
||||||
#wasm-opt = false
|
#wasm-opt = false
|
||||||
|
@ -48,8 +48,8 @@ impl Context {
|
|||||||
) -> Result<kcl_lib::ExecutorContext, String> {
|
) -> Result<kcl_lib::ExecutorContext, String> {
|
||||||
let config: kcl_lib::Configuration = serde_json::from_str(settings).map_err(|e| e.to_string())?;
|
let config: kcl_lib::Configuration = serde_json::from_str(settings).map_err(|e| e.to_string())?;
|
||||||
let mut settings: kcl_lib::ExecutorSettings = config.into();
|
let mut settings: kcl_lib::ExecutorSettings = config.into();
|
||||||
if let Some(path) = path {
|
if let Some(path_src) = path {
|
||||||
settings.with_current_file(std::path::PathBuf::from(path));
|
settings.with_current_file(kcl_lib::TypedPath::from(&path_src));
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_mock {
|
if is_mock {
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
mod context;
|
mod context;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
mod lsp;
|
mod lsp;
|
||||||
|
#[cfg(all(target_arch = "wasm32", test))]
|
||||||
|
mod tests;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
mod wasm;
|
mod wasm;
|
||||||
|
|
||||||
|
59
rust/kcl-wasm-lib/src/tests.rs
Normal file
59
rust/kcl-wasm-lib/src/tests.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
//! Helper for starting a connection.
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use kcl_lib::{
|
||||||
|
wasm_engine::{EngineCommandManager, FileSystemManager},
|
||||||
|
Program,
|
||||||
|
};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen_test::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn get_connection() -> Result<EngineCommandManager, JsValue> {
|
||||||
|
let token = if let Ok(token) = env::var("KITTYCAD_API_TOKEN") {
|
||||||
|
token
|
||||||
|
} else if let Ok(token) = env::var("ZOO_API_TOKEN") {
|
||||||
|
token
|
||||||
|
} else {
|
||||||
|
return Err("must set KITTYCAD_API_TOKEN or ZOO_API_TOKEN".into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let mgr = EngineCommandManager::new();
|
||||||
|
let mgr_clone = mgr.clone();
|
||||||
|
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
// TODO: we should probably allow for setting the host as well.
|
||||||
|
mgr_clone.start_from_wasm(&token).await.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the JS object so the test can poke at it.
|
||||||
|
Ok(mgr)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn execute_code(code: &str) -> Result<JsValue, JsValue> {
|
||||||
|
let mgr = get_connection().await?;
|
||||||
|
|
||||||
|
let program = Program::parse_no_errs(code).map_err(String::from)?;
|
||||||
|
|
||||||
|
let ctx = crate::context::Context::new(mgr, FileSystemManager::new()).await?;
|
||||||
|
|
||||||
|
let result = ctx
|
||||||
|
.execute(&serde_json::to_string(&program).map_err(|e| e.to_string())?, None, "")
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
async fn wasm_basic_execution_single_file() {
|
||||||
|
let code = include_str!("../../../public/kcl-samples/gear/main.kcl");
|
||||||
|
|
||||||
|
let result = execute_code(code).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, JsValue::from_str(""));
|
||||||
|
}
|
@ -1583,6 +1583,21 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startFromWasm(token: string): Promise<boolean> {
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
this.start({
|
||||||
|
token,
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
|
setMediaStream: () => {},
|
||||||
|
setIsStreamReady: () => {},
|
||||||
|
callbackOnEngineLiteConnect: () => {
|
||||||
|
resolve(true)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleMessage(event: MessageEvent) {
|
handleMessage(event: MessageEvent) {
|
||||||
let message: Models['WebSocketResponse_type'] | null = null
|
let message: Models['WebSocketResponse_type'] | null = null
|
||||||
|
|
||||||
@ -1731,7 +1746,9 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
this.engineConnection?.send(resizeCmd)
|
this.engineConnection?.send(resizeCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
tearDown(opts?: { idleMode: boolean }) {
|
tearDown(opts?: {
|
||||||
|
idleMode: boolean
|
||||||
|
}) {
|
||||||
if (this.engineConnection) {
|
if (this.engineConnection) {
|
||||||
for (const [cmdId, pending] of Object.entries(this.pendingCommands)) {
|
for (const [cmdId, pending] of Object.entries(this.pendingCommands)) {
|
||||||
pending.reject([
|
pending.reject([
|
||||||
|
Reference in New Issue
Block a user