refactor: finish loading in ExpFctsRender
This commit is contained in:
@@ -1,184 +1,30 @@
|
||||
import java.util.Collections;
|
||||
import java.util.Vector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ExpFctsHelper {
|
||||
|
||||
/**
|
||||
* The class represent the type of each parameters and function return value.
|
||||
*/
|
||||
public static class VariableType {
|
||||
/**
|
||||
* The base type of this variable removing all ending stars (remove all pointer levels).
|
||||
* Each item in this Vector is a part of namespace and the last one must be the type itself
|
||||
* (without any namespace constraint).
|
||||
* If no namespace constraint for this type, this Vector will only have one item.
|
||||
* <p>
|
||||
* For end user, it is enough that knowing the last item is type itself.
|
||||
*/
|
||||
private Vector<String> mBaseType;
|
||||
/**
|
||||
* The pointer level of this type. It is equal to the count of trailing star of
|
||||
* this field in C style representation.
|
||||
*/
|
||||
private int mPointerLevel;
|
||||
|
||||
/**
|
||||
* Construct an empty varible type. This is commonly used constructor.
|
||||
*/
|
||||
public VariableType() {
|
||||
mBaseType = new Vector<String>();
|
||||
mPointerLevel = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor used for cloning self. This constructor is only can be used
|
||||
* by self.
|
||||
*
|
||||
* @param base_type The hierarchy of the variable type.
|
||||
* @param pointer_level The pointer level of new created variable type.
|
||||
*/
|
||||
private VariableType(Vector<String> base_type, int pointer_level) {
|
||||
mBaseType = (Vector<String>) base_type.clone();
|
||||
mPointerLevel = pointer_level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this variable type with a type string in C/C++ style. For example
|
||||
* "NSTest::NSTest2::MyType**".
|
||||
*
|
||||
* @param ctype The type string in C/C++ style.
|
||||
*/
|
||||
public void fromCType(String ctype) {
|
||||
if (ctype.isEmpty())
|
||||
throw new IllegalArgumentException("empty string can not be parsed.");
|
||||
|
||||
// get pointer part and name part
|
||||
int len = ctype.length();
|
||||
int star_pos = ctype.indexOf('*');
|
||||
String namepart;
|
||||
if (star_pos == -1) {
|
||||
// no star
|
||||
namepart = ctype;
|
||||
mPointerLevel = 0;
|
||||
} else {
|
||||
// has star
|
||||
if (star_pos == 0)
|
||||
throw new IllegalArgumentException("base type not found.");
|
||||
namepart = ctype.substring(0, star_pos);
|
||||
mPointerLevel = len - star_pos;
|
||||
}
|
||||
|
||||
// resolve name part
|
||||
mBaseType.clear();
|
||||
for (String item : namepart.split("::")) {
|
||||
mBaseType.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a type string represented by this variable type in C/C++ style.
|
||||
*
|
||||
* @return The type string in C/C++ style.
|
||||
*/
|
||||
public String toCType() {
|
||||
return mBaseType.stream().collect(Collectors.joining("::"))
|
||||
+ String.join("", Collections.nCopies(mPointerLevel, "*"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base type of this variable type without any namespace. It just simply
|
||||
* get the last entry in type hierarchy.
|
||||
*
|
||||
* @return The base type string without namespace prefix.
|
||||
*/
|
||||
public String getBaseType() {
|
||||
return mBaseType.lastElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this variable type is a pointer. This function just check
|
||||
* whether the pointer level of this variavle type is zero.
|
||||
*
|
||||
* @return True if it is pointer, otherwise false.
|
||||
*/
|
||||
public boolean isPointer() {
|
||||
return mPointerLevel != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the pointer level of this variable type. You can simply assume the
|
||||
* pointer level is equal to the count of trailing star.
|
||||
*
|
||||
* @return The pointer level integer. Zero means that this type is not a
|
||||
* pointer.
|
||||
*/
|
||||
public int getPointerLevel() {
|
||||
return mPointerLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the clone of the type hierarchy of this variable type.
|
||||
* <p>
|
||||
* It is rarely used. This only should be used when you need the namespace
|
||||
* hierarchy of this variable type.
|
||||
*
|
||||
* @return The clone of current variable type hierarchy.
|
||||
*/
|
||||
public Vector<String> getBaseTypeHierarchy() {
|
||||
return (Vector<String>) mBaseType.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this type is a valid one. It actually check whether type
|
||||
* hierarchy include at least one entry.
|
||||
*
|
||||
* @return True if no problem of this type, otherwise false.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return mBaseType.size() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new created variable type which is the pointer of this variable
|
||||
* type.
|
||||
* <p>
|
||||
* In internal implementation, it just create a clone of current variable type
|
||||
* with the increase of pointer level by 1.
|
||||
*
|
||||
* @return The new created pointer type of this variable type.
|
||||
*/
|
||||
public VariableType getPointerOfThis() {
|
||||
return new VariableType(mBaseType, mPointerLevel + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The class represent a single parameter (argument) of function. This class
|
||||
* usually is the member of {@linkplain ExpFct}.
|
||||
* The class represent a single parameter (argument) of function.
|
||||
*/
|
||||
public static class ExpFctParam {
|
||||
|
||||
/**
|
||||
* The type of this parameter.
|
||||
*/
|
||||
public VariableType mVarType;
|
||||
public String mVarType;
|
||||
/**
|
||||
* The name of this parameter.
|
||||
*/
|
||||
public String mVarName;
|
||||
/**
|
||||
* True if this paramter is marked as input parameter, otherwise false.
|
||||
* True if this parameter is marked as input parameter, otherwise false.
|
||||
* <p>
|
||||
* Input parameter and output paramter is commonly used in C/C++ code. By using
|
||||
* Input parameter and output parameter is commonly used in C/C++ code. By using
|
||||
* this feature, each function can receive multiple arguments and return
|
||||
* multiple arguments without defining a struct to hold it.
|
||||
* <p>
|
||||
* The type of input parameter is itself. However, the type of output parameter
|
||||
* is the pointer of itself. So you may need get its pointer type when
|
||||
* processing output paramter, especially for the scenario that the target
|
||||
* processing output parameter, especially for the scenario that the target
|
||||
* language do not support explicit output parameter keyword.
|
||||
*/
|
||||
public boolean mIsInput;
|
||||
@@ -186,7 +32,7 @@ public class ExpFctsHelper {
|
||||
* The description of this parameter.
|
||||
* <p>
|
||||
* This description is generated by this program. It will indicate the
|
||||
* underlying C++ type to tell end user how to treat this paramter because some
|
||||
* underlying C++ type to tell end user how to treat this parameter because some
|
||||
* target languages' native calling style can not represent these detail.
|
||||
* <p>
|
||||
* In this program, this field must be written as a annotation of corresponding
|
||||
@@ -195,7 +41,7 @@ public class ExpFctsHelper {
|
||||
public String mVarDesc;
|
||||
|
||||
public ExpFctParam() {
|
||||
mVarType = new VariableType();
|
||||
mVarType = "";
|
||||
mVarName = "";
|
||||
mVarDesc = "";
|
||||
mIsInput = true;
|
||||
@@ -215,9 +61,9 @@ public class ExpFctsHelper {
|
||||
/**
|
||||
* The return value type of this function.
|
||||
*/
|
||||
public VariableType mFctRetType;
|
||||
public String mFctRvType;
|
||||
/**
|
||||
* The parameters (arguments) list of this function. Each items are
|
||||
* The parameters (arguments) list of this function. Each item are
|
||||
* {@linkplain ExpFctParam} and represent parameter one by one from left to
|
||||
* right.
|
||||
*/
|
||||
@@ -225,7 +71,7 @@ public class ExpFctsHelper {
|
||||
|
||||
public ExpFct() {
|
||||
mFctName = "";
|
||||
mFctRetType = new VariableType();
|
||||
mFctRvType = "";
|
||||
mFctParams = new Vector<ExpFctParam>();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ExpFctsWalker extends ExpFctsParserBaseListener {
|
||||
@@ -32,8 +33,7 @@ public class ExpFctsWalker extends ExpFctsParserBaseListener {
|
||||
// set name
|
||||
mCurrentFct.mFctName = ctx.EXPFCTS_IDENTIFIER().getText();
|
||||
// check return type
|
||||
if (!mCurrentFct.mFctRetType.isValid() || mCurrentFct.mFctRetType.isPointer()
|
||||
|| !mCurrentFct.mFctRetType.getBaseType().equals("bool"))
|
||||
if (!Objects.equals(mCurrentFct.mFctRvType, "bool"))
|
||||
throw new IllegalArgumentException("invalid interface function return type. must be bool.");
|
||||
|
||||
// add into list
|
||||
@@ -47,7 +47,7 @@ public class ExpFctsWalker extends ExpFctsParserBaseListener {
|
||||
param.mVarName = ctx.EXPFCTS_IDENTIFIER().getText();
|
||||
param.mVarDesc = "The pointer to corresponding BMFile.";
|
||||
param.mIsInput = true;
|
||||
param.mVarType.fromCType("BMap::BMFile*");
|
||||
param.mVarType = "BMap::BMFile*";
|
||||
mCurrentFct.mFctParams.add(param);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ExpFctsWalker extends ExpFctsParserBaseListener {
|
||||
param.mVarName = ctx.EXPFCTS_IDENTIFIER().getText();
|
||||
param.mVarDesc = "The pointer to corresponding BMMeshTransition.";
|
||||
param.mIsInput = true;
|
||||
param.mVarType.fromCType("BMap::BMMeshTransition*");
|
||||
param.mVarType = "BMap::BMMeshTransition*";
|
||||
mCurrentFct.mFctParams.add(param);
|
||||
}
|
||||
|
||||
@@ -67,14 +67,14 @@ public class ExpFctsWalker extends ExpFctsParserBaseListener {
|
||||
firstParam.mVarName = ctx.EXPFCTS_IDENTIFIER(0).getText();
|
||||
firstParam.mVarDesc = "The pointer to corresponding BMFile.";
|
||||
firstParam.mIsInput = true;
|
||||
firstParam.mVarType.fromCType("BMap::BMFile*");
|
||||
firstParam.mVarType = "BMap::BMFile*";
|
||||
mCurrentFct.mFctParams.add(firstParam);
|
||||
|
||||
ExpFctsHelper.ExpFctParam secondParam = new ExpFctsHelper.ExpFctParam();
|
||||
secondParam.mVarName = ctx.EXPFCTS_IDENTIFIER(1).getText();
|
||||
secondParam.mVarDesc = "The CKID of object you accessing.";
|
||||
secondParam.mIsInput = true;
|
||||
secondParam.mVarType.fromCType("LibCmo::CK2::CK_ID");
|
||||
secondParam.mVarType = "LibCmo::CK2::CK_ID";
|
||||
mCurrentFct.mFctParams.add(secondParam);
|
||||
}
|
||||
|
||||
@@ -118,12 +118,15 @@ public class ExpFctsWalker extends ExpFctsParserBaseListener {
|
||||
ctype += String.join("", Collections.nCopies(ctx.EXPFCTS_STAR().size(), "*"));
|
||||
}
|
||||
|
||||
if (!mCurrentFct.mFctRetType.isValid()) {
|
||||
// if there is function return value is not filled,
|
||||
// we fill it first because return value type is the first captured type in function statement.
|
||||
// otherwise we fill parameter type.
|
||||
if (mCurrentFct.mFctRvType.isEmpty()) {
|
||||
// fill function ret type first
|
||||
mCurrentFct.mFctRetType.fromCType(ctype);
|
||||
mCurrentFct.mFctRvType = ctype;
|
||||
} else {
|
||||
// otherwise, fill param data
|
||||
mCurrentParam.mVarType.fromCType(ctype);
|
||||
mCurrentParam.mVarType = ctype;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,26 +8,13 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class JsonWriter {
|
||||
|
||||
private static JsonObject writeVariableType(ExpFctsHelper.VariableType vt) {
|
||||
JsonObject data = new JsonObject();
|
||||
|
||||
JsonArray hierarchy = new JsonArray();
|
||||
for (String item : vt.getBaseTypeHierarchy()) {
|
||||
hierarchy.add(item);
|
||||
}
|
||||
data.add("hierarchy", hierarchy);
|
||||
data.addProperty("pointer_level", vt.getPointerLevel());
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
private static JsonObject writeExpFctParam(ExpFctsHelper.ExpFctParam param) {
|
||||
JsonObject data = new JsonObject();
|
||||
data.addProperty("type", param.mVarType);
|
||||
data.addProperty("name", param.mVarName);
|
||||
data.addProperty("is_input", param.mIsInput);
|
||||
data.addProperty("desc", param.mVarDesc);
|
||||
data.add("type", writeVariableType(param.mVarType));
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -35,7 +22,7 @@ public class JsonWriter {
|
||||
private static JsonObject writeExpFct(ExpFctsHelper.ExpFct fct) {
|
||||
JsonObject data = new JsonObject();
|
||||
data.addProperty("name", fct.mFctName);
|
||||
data.add("return", writeVariableType(fct.mFctRetType));
|
||||
data.addProperty("return", fct.mFctRvType);
|
||||
|
||||
JsonArray paramList = new JsonArray();
|
||||
for (ExpFctsHelper.ExpFctParam param : fct.mFctParams) {
|
||||
|
||||
225
Assets/CodeGen/BMapBinder/ExpFctsRender/json_loader.py
Normal file
225
Assets/CodeGen/BMapBinder/ExpFctsRender/json_loader.py
Normal file
@@ -0,0 +1,225 @@
|
||||
import json
|
||||
import typing
|
||||
import utils
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class VariableType:
|
||||
"""The class represent the type of each parameters and function return value."""
|
||||
|
||||
__base_type_hierarchy: list[str]
|
||||
"""
|
||||
The base type of this variable removing all ending stars (remove all pointer levels).
|
||||
Each item in this list is a part of namespace and the last one must be the type itself
|
||||
(without any namespace constraint).
|
||||
If no namespace constraint for this type, this list will only have one item.
|
||||
|
||||
For end user, it is enough that knowing the last item is type itself.
|
||||
"""
|
||||
__pointer_level: int
|
||||
"""
|
||||
The pointer level of this type.
|
||||
It is equal to the count of trailing star of this field in C style representation.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, base_type_hierarchy: list[str] = [], pointer_level: int = 0
|
||||
) -> None:
|
||||
"""Construct a new varible type."""
|
||||
self.__base_type_hierarchy = base_type_hierarchy
|
||||
self.__pointer_level = pointer_level
|
||||
|
||||
def clone(self) -> "VariableType":
|
||||
"""CLone self into a new instance."""
|
||||
return VariableType(list(self.__base_type_hierarchy), self.__pointer_level)
|
||||
|
||||
@staticmethod
|
||||
def from_c_type(ctype: str) -> "VariableType":
|
||||
"""
|
||||
Set this variable type with a type string in C/C++ style.
|
||||
|
||||
For example, "NSTest::NSTest2::MyType**" will produce 2 pointer level
|
||||
with ('NSTest', 'NSTest2', 'MyType') as its base type hierarchy.
|
||||
|
||||
:param ctype: The type string in C/C++ style.
|
||||
:return: The parsed VariableType instance.
|
||||
"""
|
||||
if len(ctype) == 0:
|
||||
raise RuntimeError("empty string can not be parsed")
|
||||
|
||||
# get pointer part and name part
|
||||
length = len(ctype)
|
||||
star_index = ctype.find("*")
|
||||
name_part: str
|
||||
pointer_level: int
|
||||
if star_index == -1:
|
||||
# No star, no pointer level
|
||||
name_part = ctype
|
||||
pointer_level = 0
|
||||
else:
|
||||
# Has star
|
||||
if star_index == 0:
|
||||
raise RuntimeError("base type not found")
|
||||
name_part = ctype[0:star_index]
|
||||
pointer_level = length - star_index
|
||||
|
||||
# resolve name part
|
||||
base_type_hierarchy = list(name_part.split("::"))
|
||||
|
||||
# return value
|
||||
return VariableType(base_type_hierarchy, pointer_level)
|
||||
|
||||
def to_c_type(self) -> str:
|
||||
"""
|
||||
Build a type string represented by this variable type in C/C++ style.
|
||||
|
||||
:return: The type string in C/C++ style.
|
||||
"""
|
||||
return "::".join(self.__base_type_hierarchy) + ("*" * self.__pointer_level)
|
||||
|
||||
def get_base_type(self) -> str:
|
||||
"""
|
||||
Get the base type of this variable type without any namespace.
|
||||
|
||||
It just simply get the last entry in type hierarchy.
|
||||
|
||||
:return: The base type string without namespace prefix.
|
||||
"""
|
||||
return self.__base_type_hierarchy[-1]
|
||||
|
||||
def is_pointer(self) -> bool:
|
||||
"""
|
||||
Check whether this variable type is a pointer.
|
||||
This function just check whether the pointer level of this variavle type is zero.
|
||||
|
||||
:return: True if it is pointer, otherwise false.
|
||||
"""
|
||||
return self.__pointer_level != 0
|
||||
|
||||
def get_pointer_level(self) -> int:
|
||||
"""
|
||||
Return the pointer level of this variable type.
|
||||
|
||||
You can simply assume the pointer level is equal to the count of trailing star.
|
||||
|
||||
:return: The pointer level integer. Zero means that this type is not a pointer.
|
||||
"""
|
||||
return self.__pointer_level
|
||||
|
||||
def iter_base_type_hierarchy(self) -> typing.Iterator[str]:
|
||||
"""
|
||||
Return the clone of the type hierarchy of this variable type.
|
||||
|
||||
It is rarely used.
|
||||
This only should be used when you need the namespace hierarchy of this variable type.
|
||||
|
||||
:return: The clone of current variable type hierarchy.
|
||||
"""
|
||||
return iter(self.__base_type_hierarchy)
|
||||
|
||||
# def is_valid(self) -> bool:
|
||||
# """
|
||||
# Check whether this type is a valid one.
|
||||
|
||||
# It actually check whether type hierarchy include at least one entry.
|
||||
|
||||
# :return: True if no problem of this type, otherwise false.
|
||||
# """
|
||||
# return len(self.__base_type_hierarchy) != 0
|
||||
|
||||
def get_pointer_of_this(self) -> "VariableType":
|
||||
"""
|
||||
Return a new created variable type which is the pointer of this variable type.
|
||||
|
||||
In internal implementation, it just create a clone of current variable type
|
||||
with the increase of pointer level by 1.
|
||||
|
||||
:return: The new created pointer type of this variable type.
|
||||
"""
|
||||
return VariableType(list(self.__base_type_hierarchy), self.__pointer_level + 1)
|
||||
|
||||
@staticmethod
|
||||
def from_json(data: str) -> "VariableType":
|
||||
return VariableType.from_c_type(data)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExpFctParam:
|
||||
"""The class represent a single parameter (argument) of function."""
|
||||
|
||||
var_type: VariableType
|
||||
"""The type of this parameter."""
|
||||
var_name: str
|
||||
"""The name of this parameter."""
|
||||
is_input: bool
|
||||
"""
|
||||
True if this parameter is marked as input parameter, otherwise false.
|
||||
|
||||
Input parameter and output parameter is commonly used in C/C++ code.
|
||||
By using this feature, each function can receive multiple arguments
|
||||
and return multiple arguments without defining a struct to hold it.
|
||||
|
||||
The type of input parameter is itself.
|
||||
However, the type of output parameter is the pointer of itself.
|
||||
So you may need get its pointer type when processing output parameter,
|
||||
especially for the scenario that the target language do not support explicit output parameter keyword.
|
||||
"""
|
||||
var_desc: str
|
||||
"""
|
||||
The description of this parameter.
|
||||
|
||||
This description is generated by this program.
|
||||
It will indicate the underlying C++ type to tell end user how to treat this parameter
|
||||
because some target languages' native calling style can not represent these detail.
|
||||
|
||||
In this program, this field must be written as a docstring of corresponding function.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def from_json(data: dict[str, typing.Any]) -> "ExpFctParam":
|
||||
return ExpFctParam(
|
||||
VariableType.from_c_type(data["type"]),
|
||||
data["name"],
|
||||
data["is_input"],
|
||||
data["desc"],
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExpFct:
|
||||
"""The class represent an export BMap function."""
|
||||
|
||||
fct_name: str
|
||||
"""The name of this function."""
|
||||
fct_rv_type: VariableType
|
||||
"""The return value type of this function."""
|
||||
fct_params: list[ExpFctParam]
|
||||
"""
|
||||
The parameters (arguments) list of this function.
|
||||
Each item represent parameter accepted by this function one by one from left to right.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def from_json(data: dict[str, typing.Any]) -> "ExpFct":
|
||||
return ExpFct(
|
||||
data["name"],
|
||||
VariableType.from_json(data["return"]),
|
||||
list(map(lambda i: ExpFctParam.from_json(i), data["params"])),
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExpFctCollection:
|
||||
"""The class represent a collection of export BMap functions."""
|
||||
|
||||
fcts: list[ExpFct]
|
||||
"""The collection of exported BMap functions."""
|
||||
|
||||
@staticmethod
|
||||
def from_json(data: list[typing.Any]) -> "ExpFctCollection":
|
||||
return ExpFctCollection(list(map(lambda i: ExpFct.from_json(i), data)))
|
||||
|
||||
|
||||
def load_fcts(filename: str) -> ExpFctCollection:
|
||||
with open(utils.get_input_file_path(filename), "r", encoding="utf-8") as f:
|
||||
return ExpFctCollection.from_json(json.load(f))
|
||||
@@ -1,5 +1,10 @@
|
||||
import json_loader
|
||||
import utils
|
||||
|
||||
def main():
|
||||
print("Hello from exp-fcts-render!")
|
||||
fcts = json_loader.load_fcts("BMExports.json")
|
||||
|
||||
print("Done")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
22
Assets/CodeGen/BMapBinder/ExpFctsRender/utils.py
Normal file
22
Assets/CodeGen/BMapBinder/ExpFctsRender/utils.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _get_root_directory() -> Path:
|
||||
bmap_binder_root = os.environ.get("BMAP_BINDER_ROOT", None)
|
||||
if bmap_binder_root is None:
|
||||
return Path(__file__).resolve().parent.parent
|
||||
else:
|
||||
return Path(bmap_binder_root).resolve()
|
||||
|
||||
|
||||
def get_input_file_path(filename: str) -> Path:
|
||||
return _get_root_directory() / "Analyzed" / filename
|
||||
|
||||
|
||||
def get_output_file_path(filename: str) -> Path:
|
||||
return _get_root_directory() / "Output" / filename
|
||||
|
||||
|
||||
def get_template_directory() -> Path:
|
||||
return Path(__file__).resolve().parent / "templates"
|
||||
Reference in New Issue
Block a user