diff --git a/backend/src/packages/chaiNNer_standard/utility/directories/back_directory.py b/backend/src/packages/chaiNNer_standard/utility/directories/back_directory.py new file mode 100644 index 000000000..166d95ee1 --- /dev/null +++ b/backend/src/packages/chaiNNer_standard/utility/directories/back_directory.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from pathlib import Path + +from nodes.properties.inputs import DirectoryInput, NumberInput +from nodes.properties.outputs import DirectoryOutput + +from .. import value_group + + +@value_group.register( + schema_id="chainner:utility:back_directory", + name="Back Directory", + description="Traverse up/back from a directory the specified number of times.", + icon="BsFolder", + inputs=[ + DirectoryInput( + "Directory", must_exist=False, label_style="hidden", has_handle=True + ), + NumberInput("Amount back", has_handle=True, minimum=1, precision=0, default=1), + ], + outputs=[ + DirectoryOutput( + "Directory", + output_type="Directory { path: navigateBackPath(Input0.path, Input1) }", + ), + ], +) +def back_directory_node(directory: Path, amt: int) -> Path: + result = directory + for _ in range(amt): + result = result.parent + return result diff --git a/backend/src/packages/chaiNNer_standard/utility/directories/directory_to_text.py b/backend/src/packages/chaiNNer_standard/utility/directories/directory_to_text.py new file mode 100644 index 000000000..2d122a63b --- /dev/null +++ b/backend/src/packages/chaiNNer_standard/utility/directories/directory_to_text.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from pathlib import Path + +from nodes.properties.inputs import DirectoryInput +from nodes.properties.outputs import TextOutput + +from .. import value_group + + +@value_group.register( + schema_id="chainner:utility:directory_to_text", + name="Directory to Text", + description="Converts a directory path into usable text.", + icon="BsFolder", + inputs=[ + DirectoryInput( + "Directory", must_exist=False, label_style="hidden", has_handle=True + ), + ], + outputs=[ + TextOutput( + "Directory Text", + output_type="Input0.path", + ), + ], +) +def directory_to_text_node(directory: Path) -> str: + return str(directory) diff --git a/backend/src/packages/chaiNNer_standard/utility/directories/into_directory.py b/backend/src/packages/chaiNNer_standard/utility/directories/into_directory.py new file mode 100644 index 000000000..d40de9d8e --- /dev/null +++ b/backend/src/packages/chaiNNer_standard/utility/directories/into_directory.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from pathlib import Path + +from nodes.properties.inputs import BoolInput, DirectoryInput, TextInput +from nodes.properties.outputs import DirectoryOutput + +from .. import value_group + + +@value_group.register( + schema_id="chainner:utility:into_directory", + name="Into Directory", + description="Goes forward into a directory.", + icon="BsFolder", + inputs=[ + DirectoryInput( + "Directory", must_exist=False, label_style="hidden", has_handle=True + ), + TextInput("Folder", has_handle=True), + BoolInput("Create if not exists", default=True), + ], + outputs=[ + DirectoryOutput( + "Directory", + output_type="Directory { path: combinePath(Input0.path, Input1) }", + ), + ], +) +def into_directory_node(directory: Path, folder: str, create_path: bool) -> Path: + resolved = (directory / folder).resolve() + if create_path and not resolved.exists(): + resolved.mkdir(parents=True) + elif not resolved.exists(): + raise FileNotFoundError(f"Directory does not exist: {resolved}") + return resolved diff --git a/src/common/types/chainner-builtin.ts b/src/common/types/chainner-builtin.ts index def838799..5b6ab9f4b 100644 --- a/src/common/types/chainner-builtin.ts +++ b/src/common/types/chainner-builtin.ts @@ -14,6 +14,7 @@ import { handleNumberLiterals, intersect, literal, + wrapBinary, wrapQuaternary, wrapScopedUnary, wrapTernary, @@ -360,3 +361,21 @@ export const parseColorJson = wrapScopedUnary( return createInstance(colorDesc); } ); + +export const combinePath = wrapBinary( + (dir: StringPrimitive, next: StringPrimitive): Arg => { + if (dir.type === 'literal' && next.type === 'literal') { + return literal(path.join(dir.value, next.value)); + } + return StringType.instance; + } +); + +export const navigateBackPath = wrapBinary( + (dir: StringPrimitive, backNum: NumberPrimitive): Arg => { + if (dir.type === 'literal' && backNum.type === 'literal') { + return literal(path.join(dir.value, '../'.repeat(backNum.value))); + } + return StringType.instance; + } +); diff --git a/src/common/types/chainner-scope.ts b/src/common/types/chainner-scope.ts index 80eb07218..b22db0499 100644 --- a/src/common/types/chainner-scope.ts +++ b/src/common/types/chainner-scope.ts @@ -11,7 +11,9 @@ import { } from '@chainner/navi'; import { lazy } from '../util'; import { + combinePath, formatTextPattern, + navigateBackPath, padCenter, padEnd, padStart, @@ -127,6 +129,8 @@ intrinsic def padEnd(text: string, width: uint, padding: string): string; intrinsic def padCenter(text: string, width: uint, padding: string): string; intrinsic def splitFilePath(path: string): SplitFilePath; intrinsic def parseColorJson(json: string): Color; +intrinsic def combinePath(path: string, next: string): string; +intrinsic def navigateBackPath(path: string, amount: uint): string; `; export const getChainnerScope = lazy((): Scope => { @@ -140,6 +144,8 @@ export const getChainnerScope = lazy((): Scope => { padCenter: makeScoped(padCenter), splitFilePath, parseColorJson, + combinePath: makeScoped(combinePath), + navigateBackPath: makeScoped(navigateBackPath), }; const definitions = parseDefinitions(new SourceDocument(code, 'chainner-internal'));