You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
859 lines
24 KiB
C++
859 lines
24 KiB
C++
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PyGenUtil.h"
|
|
#include "PyUtil.h"
|
|
#include "PyWrapperBase.h"
|
|
#include "Internationalization/BreakIterator.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/Package.h"
|
|
#include "Engine/BlueprintGeneratedClass.h"
|
|
#include "Engine/UserDefinedStruct.h"
|
|
#include "Engine/UserDefinedEnum.h"
|
|
|
|
#if WITH_PYTHON
|
|
|
|
namespace PyGenUtil
|
|
{
|
|
|
|
const FName ScriptNameMetaDataKey = TEXT("ScriptName");
|
|
const FName ScriptNoExportMetaDataKey = TEXT("ScriptNoExport");
|
|
const FName BlueprintTypeMetaDataKey = TEXT("BlueprintType");
|
|
const FName NotBlueprintTypeMetaDataKey = TEXT("NotBlueprintType");
|
|
const FName BlueprintSpawnableComponentMetaDataKey = TEXT("BlueprintSpawnableComponent");
|
|
const FName BlueprintGetterMetaDataKey = TEXT("BlueprintGetter");
|
|
const FName BlueprintSetterMetaDataKey = TEXT("BlueprintSetter");
|
|
|
|
|
|
TSharedPtr<IBreakIterator> NameBreakIterator;
|
|
|
|
|
|
void FGeneratedWrappedMethod::ToPython(FPyMethodWithClosureDef& OutPyMethod) const
|
|
{
|
|
OutPyMethod.MethodName = MethodName.GetData();
|
|
OutPyMethod.MethodDoc = MethodDoc.GetData();
|
|
OutPyMethod.MethodCallback = MethodCallback;
|
|
OutPyMethod.MethodFlags = MethodFlags;
|
|
OutPyMethod.MethodClosure = (void*)this;
|
|
}
|
|
|
|
|
|
void FGeneratedWrappedGetSet::ToPython(PyGetSetDef& OutPyGetSet) const
|
|
{
|
|
OutPyGetSet.name = (char*)GetSetName.GetData();
|
|
OutPyGetSet.doc = (char*)GetSetDoc.GetData();
|
|
OutPyGetSet.get = GetCallback;
|
|
OutPyGetSet.set = SetCallback;
|
|
OutPyGetSet.closure = (void*)this;
|
|
}
|
|
|
|
|
|
FGeneratedWrappedPropertyDoc::FGeneratedWrappedPropertyDoc(const UProperty* InProp)
|
|
{
|
|
PythonPropName = PyGenUtil::GetPropertyPythonName(InProp);
|
|
|
|
const FString PropTooltip = PyGenUtil::GetFieldTooltip(InProp);
|
|
DocString = PyGenUtil::PythonizePropertyTooltip(PropTooltip, InProp);
|
|
EditorDocString = PyGenUtil::PythonizePropertyTooltip(PropTooltip, InProp, CPF_EditConst);
|
|
}
|
|
|
|
bool FGeneratedWrappedPropertyDoc::SortPredicate(const FGeneratedWrappedPropertyDoc& InOne, const FGeneratedWrappedPropertyDoc& InTwo)
|
|
{
|
|
return InOne.PythonPropName < InTwo.PythonPropName;
|
|
}
|
|
|
|
FString FGeneratedWrappedPropertyDoc::BuildDocString(const TArray<FGeneratedWrappedPropertyDoc>& InDocs, const bool bEditorVariant)
|
|
{
|
|
FString Str;
|
|
AppendDocString(InDocs, Str, bEditorVariant);
|
|
return Str;
|
|
}
|
|
|
|
void FGeneratedWrappedPropertyDoc::AppendDocString(const TArray<FGeneratedWrappedPropertyDoc>& InDocs, FString& OutStr, const bool bEditorVariant)
|
|
{
|
|
if (!InDocs.Num())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!OutStr.IsEmpty())
|
|
{
|
|
if (OutStr[OutStr.Len() - 1] != TEXT('\n'))
|
|
{
|
|
OutStr += TEXT('\n');
|
|
}
|
|
OutStr += TEXT("\n----------------------------------------------------------------------\n");
|
|
}
|
|
|
|
OutStr += TEXT("Editor Properties: (see get_editor_property/set_editor_property)\n");
|
|
for (const FGeneratedWrappedPropertyDoc& Doc : InDocs)
|
|
{
|
|
TArray<FString> DocStringLines;
|
|
(bEditorVariant ? Doc.EditorDocString : Doc.DocString).ParseIntoArrayLines(DocStringLines, /*bCullEmpty*/false);
|
|
|
|
OutStr += TEXT('\n');
|
|
OutStr += Doc.PythonPropName;
|
|
for (const FString& DocStringLine : DocStringLines)
|
|
{
|
|
OutStr += TEXT("\n ");
|
|
OutStr += DocStringLine;
|
|
}
|
|
OutStr += TEXT('\n');
|
|
}
|
|
OutStr += TEXT("\n----------------------------------------------------------------------");
|
|
}
|
|
|
|
|
|
bool FGeneratedWrappedType::Finalize()
|
|
{
|
|
PyType.tp_name = TypeName.GetData();
|
|
PyType.tp_doc = TypeDoc.GetData();
|
|
|
|
PyMethods.Reserve(TypeMethods.Num() + 1);
|
|
for (const FGeneratedWrappedMethod& TypeMethod : TypeMethods)
|
|
{
|
|
FPyMethodWithClosureDef& PyMethod = PyMethods[PyMethods.AddZeroed()];
|
|
TypeMethod.ToPython(PyMethod);
|
|
}
|
|
PyMethods.AddZeroed(); // null terminator
|
|
|
|
PyMethods.Reserve(PyGetSets.Num() + 1);
|
|
for (const FGeneratedWrappedGetSet& TypeGetSet : TypeGetSets)
|
|
{
|
|
PyGetSetDef& PyGetSet = PyGetSets[PyGetSets.AddZeroed()];
|
|
TypeGetSet.ToPython(PyGetSet);
|
|
}
|
|
PyGetSets.AddZeroed(); // null terminator
|
|
PyType.tp_getset = PyGetSets.GetData();
|
|
|
|
if (PyType_Ready(&PyType) == 0)
|
|
{
|
|
FPyMethodWithClosureDef::AddMethods(PyMethods.GetData(), &PyType);
|
|
FPyWrapperBaseMetaData::SetMetaData(&PyType, MetaData.Get());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
FUTF8Buffer TCHARToUTF8Buffer(const TCHAR* InStr)
|
|
{
|
|
auto ToUTF8Buffer = [](const char* InUTF8Str)
|
|
{
|
|
int32 UTF8StrLen = 0;
|
|
while (InUTF8Str[UTF8StrLen++] != 0) {} // Count includes the null terminator
|
|
|
|
FUTF8Buffer UTF8Buffer;
|
|
UTF8Buffer.Append(InUTF8Str, UTF8StrLen);
|
|
return UTF8Buffer;
|
|
};
|
|
|
|
return ToUTF8Buffer(TCHAR_TO_UTF8(InStr));
|
|
}
|
|
|
|
PyObject* GetPostInitFunc(PyTypeObject* InPyType)
|
|
{
|
|
FPyObjectPtr PostInitFunc = FPyObjectPtr::StealReference(PyObject_GetAttrString((PyObject*)InPyType, PostInitFuncName));
|
|
if (!PostInitFunc)
|
|
{
|
|
PyUtil::SetPythonError(PyExc_TypeError, InPyType, *FString::Printf(TEXT("Python type has no '%s' function"), UTF8_TO_TCHAR(PostInitFuncName)));
|
|
return nullptr;
|
|
}
|
|
|
|
if (!PyCallable_Check(PostInitFunc))
|
|
{
|
|
PyUtil::SetPythonError(PyExc_TypeError, InPyType, *FString::Printf(TEXT("Python type attribute '%s' is not callable"), UTF8_TO_TCHAR(PostInitFuncName)));
|
|
return nullptr;
|
|
}
|
|
|
|
// Only test arguments for actual functions and methods (the base type exposed from C will be a 'method_descriptor')
|
|
if (PyFunction_Check(PostInitFunc) || PyMethod_Check(PostInitFunc))
|
|
{
|
|
TArray<FString> FuncArgNames;
|
|
if (!PyUtil::InspectFunctionArgs(PostInitFunc, FuncArgNames))
|
|
{
|
|
PyUtil::SetPythonError(PyExc_Exception, InPyType, *FString::Printf(TEXT("Failed to inspect the arguments for '%s'"), UTF8_TO_TCHAR(PostInitFuncName)));
|
|
return nullptr;
|
|
}
|
|
if (FuncArgNames.Num() != 1)
|
|
{
|
|
PyUtil::SetPythonError(PyExc_TypeError, InPyType, *FString::Printf(TEXT("'%s' must take a single parameter ('self')"), UTF8_TO_TCHAR(PostInitFuncName)));
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return PostInitFunc.Release();
|
|
}
|
|
|
|
void AddStructInitParam(const TCHAR* InUnrealPropName, const TCHAR* InPythonAttrName, TArray<FGeneratedWrappedMethodParameter>& OutInitParams)
|
|
{
|
|
FGeneratedWrappedMethodParameter& InitParam = OutInitParams[OutInitParams.AddDefaulted()];
|
|
InitParam.ParamName = TCHARToUTF8Buffer(InPythonAttrName);
|
|
InitParam.ParamPropName = InUnrealPropName;
|
|
InitParam.ParamDefaultValue = FString();
|
|
}
|
|
|
|
bool ParseMethodParameters(PyObject* InArgs, PyObject* InKwds, const TArray<FGeneratedWrappedMethodParameter>& InParamDef, const char* InPyMethodName, TArray<PyObject*>& OutPyParams)
|
|
{
|
|
if (!InArgs || !PyTuple_Check(InArgs) || (InKwds && !PyDict_Check(InKwds)) || !InPyMethodName)
|
|
{
|
|
PyErr_BadInternalCall();
|
|
return false;
|
|
}
|
|
|
|
const Py_ssize_t NumArgs = PyTuple_GET_SIZE(InArgs);
|
|
const Py_ssize_t NumKeywords = InKwds ? PyDict_Size(InKwds) : 0;
|
|
if (NumArgs + NumKeywords > InParamDef.Num())
|
|
{
|
|
PyErr_Format(PyExc_TypeError, "%s() takes at most %d argument%s (%d given)", InPyMethodName, InParamDef.Num(), (InParamDef.Num() == 1 ? "" : "s"), (int32)(NumArgs + NumKeywords));
|
|
return false;
|
|
}
|
|
|
|
// Parse both keyword and index args in the same loop (favor keywords, fallback to index)
|
|
Py_ssize_t RemainingKeywords = NumKeywords;
|
|
for (int32 Index = 0; Index < InParamDef.Num(); ++Index)
|
|
{
|
|
const FGeneratedWrappedMethodParameter& ParamDef = InParamDef[Index];
|
|
|
|
PyObject* ParsedArg = nullptr;
|
|
if (RemainingKeywords > 0)
|
|
{
|
|
ParsedArg = PyDict_GetItemString(InKwds, ParamDef.ParamName.GetData());
|
|
}
|
|
|
|
if (ParsedArg)
|
|
{
|
|
--RemainingKeywords;
|
|
if (Index < NumArgs)
|
|
{
|
|
PyErr_Format(PyExc_TypeError, "Argument given by name ('%s') and position (%d)", ParamDef.ParamName.GetData(), Index + 1);
|
|
return false;
|
|
}
|
|
}
|
|
else if (RemainingKeywords > 0 && PyErr_Occurred())
|
|
{
|
|
return false;
|
|
}
|
|
else if (Index < NumArgs)
|
|
{
|
|
ParsedArg = PyTuple_GET_ITEM(InArgs, Index);
|
|
}
|
|
|
|
if (ParsedArg || ParamDef.ParamDefaultValue.IsSet())
|
|
{
|
|
OutPyParams.Add(ParsedArg);
|
|
continue;
|
|
}
|
|
|
|
PyErr_Format(PyExc_TypeError, "Required argument '%s' (pos %d) not found", ParamDef.ParamName.GetData(), Index + 1);
|
|
return false;
|
|
}
|
|
|
|
// Report any extra keyword args
|
|
if (RemainingKeywords > 0)
|
|
{
|
|
PyObject* Key = nullptr;
|
|
PyObject* Value = nullptr;
|
|
Py_ssize_t Index = 0;
|
|
while (PyDict_Next(InKwds, &Index, &Key, &Value))
|
|
{
|
|
const FUTF8Buffer Keyword = TCHARToUTF8Buffer(*PyUtil::PyObjectToUEString(Key));
|
|
const bool bIsExpected = InParamDef.ContainsByPredicate([&Keyword](const FGeneratedWrappedMethodParameter& ParamDef)
|
|
{
|
|
return FCStringAnsi::Strcmp(Keyword.GetData(), ParamDef.ParamName.GetData()) == 0;
|
|
});
|
|
|
|
if (!bIsExpected)
|
|
{
|
|
PyErr_Format(PyExc_TypeError, "'%s' is an invalid keyword argument for this function", Keyword.GetData());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsBlueprintExposedClass(const UClass* InClass)
|
|
{
|
|
for (const UClass* ParentClass = InClass; ParentClass; ParentClass = ParentClass->GetSuperClass())
|
|
{
|
|
if (ParentClass->GetBoolMetaData(BlueprintTypeMetaDataKey) || ParentClass->HasMetaData(BlueprintSpawnableComponentMetaDataKey))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (ParentClass->GetBoolMetaData(NotBlueprintTypeMetaDataKey))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsBlueprintExposedStruct(const UStruct* InStruct)
|
|
{
|
|
for (const UStruct* ParentStruct = InStruct; ParentStruct; ParentStruct = ParentStruct->GetSuperStruct())
|
|
{
|
|
if (ParentStruct->GetBoolMetaData(BlueprintTypeMetaDataKey))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (ParentStruct->GetBoolMetaData(NotBlueprintTypeMetaDataKey))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsBlueprintExposedEnum(const UEnum* InEnum)
|
|
{
|
|
if (InEnum->GetBoolMetaData(BlueprintTypeMetaDataKey))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (InEnum->GetBoolMetaData(NotBlueprintTypeMetaDataKey))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsBlueprintExposedProperty(const UProperty* InProp)
|
|
{
|
|
return InProp->HasAnyPropertyFlags(CPF_BlueprintVisible);
|
|
}
|
|
|
|
bool IsBlueprintExposedFunction(const UFunction* InFunc)
|
|
{
|
|
return InFunc->HasAnyFunctionFlags(FUNC_BlueprintCallable | FUNC_BlueprintEvent) && !InFunc->HasMetaData(BlueprintGetterMetaDataKey) && !InFunc->HasMetaData(BlueprintSetterMetaDataKey);
|
|
}
|
|
|
|
bool IsBlueprintExposedField(const UField* InField)
|
|
{
|
|
if (const UProperty* Prop = Cast<const UProperty>(InField))
|
|
{
|
|
return IsBlueprintExposedProperty(Prop);
|
|
}
|
|
|
|
if (const UFunction* Func = Cast<const UFunction>(InField))
|
|
{
|
|
return IsBlueprintExposedFunction(Func);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool HasBlueprintExposedFields(const UStruct* InStruct)
|
|
{
|
|
for (TFieldIterator<const UField> FieldIt(InStruct); FieldIt; ++FieldIt)
|
|
{
|
|
if (IsBlueprintExposedField(*FieldIt))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsBlueprintGeneratedClass(const UClass* InClass)
|
|
{
|
|
// Need to use IsA rather than IsChildOf since we want to test the type of InClass itself *NOT* the class instance represented by InClass
|
|
const UObject* ClassObject = InClass;
|
|
return ClassObject->IsA<UBlueprintGeneratedClass>();
|
|
}
|
|
|
|
bool IsBlueprintGeneratedStruct(const UStruct* InStruct)
|
|
{
|
|
return InStruct->IsA<UUserDefinedStruct>();
|
|
}
|
|
|
|
bool IsBlueprintGeneratedEnum(const UEnum* InEnum)
|
|
{
|
|
return InEnum->IsA<UUserDefinedEnum>();
|
|
}
|
|
|
|
bool ShouldExportClass(const UClass* InClass)
|
|
{
|
|
return IsBlueprintExposedClass(InClass) || HasBlueprintExposedFields(InClass);
|
|
}
|
|
|
|
bool ShouldExportStruct(const UStruct* InStruct)
|
|
{
|
|
return IsBlueprintExposedStruct(InStruct) || HasBlueprintExposedFields(InStruct);
|
|
}
|
|
|
|
bool ShouldExportEnum(const UEnum* InEnum)
|
|
{
|
|
return IsBlueprintExposedEnum(InEnum);
|
|
}
|
|
|
|
bool ShouldExportProperty(const UProperty* InProp)
|
|
{
|
|
const bool bCanScriptExport = !InProp->HasMetaData(ScriptNoExportMetaDataKey);
|
|
return bCanScriptExport && IsBlueprintExposedProperty(InProp);
|
|
}
|
|
|
|
bool ShouldExportEditorOnlyProperty(const UProperty* InProp)
|
|
{
|
|
const bool bCanScriptExport = !InProp->HasMetaData(ScriptNoExportMetaDataKey);
|
|
return bCanScriptExport && GIsEditor && InProp->HasAnyPropertyFlags(CPF_Edit);
|
|
}
|
|
|
|
bool ShouldExportFunction(const UFunction* InFunc)
|
|
{
|
|
const bool bCanScriptExport = !InFunc->HasMetaData(ScriptNoExportMetaDataKey);
|
|
return bCanScriptExport && IsBlueprintExposedFunction(InFunc);
|
|
}
|
|
|
|
FString PythonizeName(const FString& InName, const EPythonizeNameCase InNameCase)
|
|
{
|
|
FString PythonizedName;
|
|
PythonizedName.Reserve(InName.Len() + 10);
|
|
|
|
if (!NameBreakIterator.IsValid())
|
|
{
|
|
NameBreakIterator = FBreakIterator::CreateCamelCaseBreakIterator();
|
|
}
|
|
|
|
NameBreakIterator->SetString(InName);
|
|
for (int32 PrevBreak = 0, NameBreak = NameBreakIterator->MoveToNext(); NameBreak != INDEX_NONE; NameBreak = NameBreakIterator->MoveToNext())
|
|
{
|
|
const int32 OrigPythonizedNameLen = PythonizedName.Len();
|
|
|
|
// Append an underscore if this was a break between two parts of the identifier, *and* the previous character isn't already an underscore
|
|
if (OrigPythonizedNameLen > 0 != 0 && PythonizedName[OrigPythonizedNameLen - 1] != TEXT('_'))
|
|
{
|
|
PythonizedName += TEXT('_');
|
|
}
|
|
|
|
// Append this part of the identifier
|
|
PythonizedName.AppendChars(&InName[PrevBreak], NameBreak - PrevBreak);
|
|
|
|
// Remove any trailing underscores in the last part of the identifier
|
|
while (PythonizedName.Len() > OrigPythonizedNameLen)
|
|
{
|
|
const int32 CharIndex = PythonizedName.Len() - 1;
|
|
if (PythonizedName[CharIndex] != TEXT('_'))
|
|
{
|
|
break;
|
|
}
|
|
PythonizedName.RemoveAt(CharIndex, 1, false);
|
|
}
|
|
|
|
PrevBreak = NameBreak;
|
|
}
|
|
NameBreakIterator->ClearString();
|
|
|
|
if (InNameCase == EPythonizeNameCase::Lower)
|
|
{
|
|
PythonizedName.ToLowerInline();
|
|
}
|
|
else if (InNameCase == EPythonizeNameCase::Upper)
|
|
{
|
|
PythonizedName.ToUpperInline();
|
|
}
|
|
|
|
return PythonizedName;
|
|
}
|
|
|
|
FString PythonizePropertyName(const FString& InName, const EPythonizeNameCase InNameCase)
|
|
{
|
|
int32 NameOffset = 0;
|
|
|
|
for (;;)
|
|
{
|
|
// Strip the "b" prefix from bool names
|
|
if (InName.Len() - NameOffset >= 2 && InName[NameOffset] == TEXT('b') && FChar::IsUpper(InName[NameOffset + 1]))
|
|
{
|
|
NameOffset += 1;
|
|
continue;
|
|
}
|
|
|
|
// Strip the "In" prefix from names
|
|
if (InName.Len() - NameOffset >= 3 && InName[NameOffset] == TEXT('I') && InName[NameOffset + 1] == TEXT('n') && FChar::IsUpper(InName[NameOffset + 2]))
|
|
{
|
|
NameOffset += 2;
|
|
continue;
|
|
}
|
|
|
|
// Strip the "Out" prefix from names
|
|
if (InName.Len() - NameOffset >= 4 && InName[NameOffset] == TEXT('O') && InName[NameOffset + 1] == TEXT('u') && InName[NameOffset + 2] == TEXT('t') && FChar::IsUpper(InName[NameOffset + 3]))
|
|
{
|
|
NameOffset += 3;
|
|
continue;
|
|
}
|
|
|
|
// Nothing more to strip
|
|
break;
|
|
}
|
|
|
|
return PythonizeName(NameOffset ? InName.RightChop(NameOffset) : InName, InNameCase);
|
|
}
|
|
|
|
FString PythonizePropertyTooltip(const FString& InTooltip, const UProperty* InProp, const uint64 InReadOnlyFlags)
|
|
{
|
|
return PythonizeTooltip(InTooltip, FPythonizeTooltipContext(InProp, nullptr, InReadOnlyFlags));
|
|
}
|
|
|
|
FString PythonizeFunctionTooltip(const FString& InTooltip, const UStruct* InParamsStruct)
|
|
{
|
|
return PythonizeTooltip(InTooltip, FPythonizeTooltipContext(nullptr, InParamsStruct));
|
|
}
|
|
|
|
FString PythonizeTooltip(const FString& InTooltip, const FPythonizeTooltipContext& InContext)
|
|
{
|
|
FString PythonizedTooltip;
|
|
PythonizedTooltip.Reserve(InTooltip.Len());
|
|
|
|
int32 TooltipIndex = 0;
|
|
const int32 TooltipLen = InTooltip.Len();
|
|
|
|
TArray<TTuple<FString, FString>, TInlineAllocator<4>> ParsedMiscTokens;
|
|
TArray<TTuple<FName, FString>, TInlineAllocator<8>> ParsedParamTokens;
|
|
FString ReturnToken;
|
|
|
|
auto SkipToNextToken = [&InTooltip, &TooltipIndex, &TooltipLen]()
|
|
{
|
|
while (TooltipIndex < TooltipLen && (FChar::IsWhitespace(InTooltip[TooltipIndex]) || InTooltip[TooltipIndex] == TEXT('-')))
|
|
{
|
|
++TooltipIndex;
|
|
}
|
|
};
|
|
|
|
auto ParseSimpleToken = [&InTooltip, &TooltipIndex, &TooltipLen](FString& OutToken)
|
|
{
|
|
while (TooltipIndex < TooltipLen && !FChar::IsWhitespace(InTooltip[TooltipIndex]))
|
|
{
|
|
OutToken += InTooltip[TooltipIndex++];
|
|
}
|
|
};
|
|
|
|
auto ParseComplexToken = [&InTooltip, &TooltipIndex, &TooltipLen](FString& OutToken)
|
|
{
|
|
while (TooltipIndex < TooltipLen && InTooltip[TooltipIndex] != TEXT('@'))
|
|
{
|
|
// Convert a new-line within a token to a space
|
|
if (FChar::IsLinebreak(InTooltip[TooltipIndex]))
|
|
{
|
|
while (TooltipIndex < TooltipLen && FChar::IsLinebreak(InTooltip[TooltipIndex]))
|
|
{
|
|
++TooltipIndex;
|
|
}
|
|
|
|
while (TooltipIndex < TooltipLen && FChar::IsWhitespace(InTooltip[TooltipIndex]))
|
|
{
|
|
++TooltipIndex;
|
|
}
|
|
|
|
OutToken += TEXT(' ');
|
|
}
|
|
|
|
// Sanity check in case the first character after the new-line is @
|
|
if (TooltipIndex < TooltipLen && InTooltip[TooltipIndex] != TEXT('@'))
|
|
{
|
|
OutToken += InTooltip[TooltipIndex++];
|
|
}
|
|
}
|
|
OutToken.TrimEndInline();
|
|
};
|
|
|
|
// Append the property type (if given)
|
|
if (InContext.Prop)
|
|
{
|
|
PythonizedTooltip += TEXT("type: ");
|
|
AppendPropertyPythonType(InContext.Prop, PythonizedTooltip, /*bIncludeReadWriteState*/true, InContext.ReadOnlyFlags);
|
|
PythonizedTooltip += TEXT('\n');
|
|
}
|
|
|
|
// Parse the tooltip for its tokens and values (basic content goes directly into PythonizedTooltip)
|
|
for (; TooltipIndex < TooltipLen;)
|
|
{
|
|
if (InTooltip[TooltipIndex] == TEXT('@'))
|
|
{
|
|
++TooltipIndex; // Walk over the @
|
|
if (InTooltip[TooltipIndex] == TEXT('@'))
|
|
{
|
|
// Literal @ character
|
|
PythonizedTooltip += TEXT('@');
|
|
continue;
|
|
}
|
|
|
|
// Parse out the token name
|
|
FString TokenName;
|
|
SkipToNextToken();
|
|
ParseSimpleToken(TokenName);
|
|
|
|
if (TokenName == TEXT("param"))
|
|
{
|
|
// Parse out the parameter name
|
|
FString ParamName;
|
|
SkipToNextToken();
|
|
ParseSimpleToken(ParamName);
|
|
|
|
// Parse out the parameter comment
|
|
FString ParamComment;
|
|
SkipToNextToken();
|
|
ParseComplexToken(ParamComment);
|
|
|
|
ParsedParamTokens.Add(MakeTuple(FName(*ParamName), MoveTemp(ParamComment)));
|
|
}
|
|
else if (TokenName == TEXT("return") || TokenName == TEXT("returns"))
|
|
{
|
|
// Parse out the return value token
|
|
SkipToNextToken();
|
|
ParseComplexToken(ReturnToken);
|
|
}
|
|
else
|
|
{
|
|
// Parse out the token value
|
|
FString TokenValue;
|
|
SkipToNextToken();
|
|
ParseComplexToken(TokenValue);
|
|
|
|
ParsedMiscTokens.Add(MakeTuple(MoveTemp(TokenName), MoveTemp(TokenValue)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Convert duplicate new-lines to a single new-line
|
|
if (FChar::IsLinebreak(InTooltip[TooltipIndex]))
|
|
{
|
|
while (TooltipIndex < TooltipLen && FChar::IsLinebreak(InTooltip[TooltipIndex]))
|
|
{
|
|
++TooltipIndex;
|
|
}
|
|
PythonizedTooltip += TEXT('\n');
|
|
}
|
|
else
|
|
{
|
|
// Normal character
|
|
PythonizedTooltip += InTooltip[TooltipIndex++];
|
|
}
|
|
}
|
|
}
|
|
|
|
PythonizedTooltip.TrimEndInline();
|
|
|
|
// Process the misc tokens into PythonizedTooltip
|
|
for (const auto& MiscTokenPair : ParsedMiscTokens)
|
|
{
|
|
PythonizedTooltip += TEXT('\n');
|
|
PythonizedTooltip += MiscTokenPair.Key;
|
|
PythonizedTooltip += TEXT(": ");
|
|
PythonizedTooltip += MiscTokenPair.Value;
|
|
}
|
|
|
|
// Process the param tokens into PythonizedTooltip
|
|
auto AppendParamTypeDoc = [&PythonizedTooltip](const UProperty* InParamProp)
|
|
{
|
|
PythonizedTooltip += TEXT(" (");
|
|
AppendPropertyPythonType(InParamProp, PythonizedTooltip);
|
|
PythonizedTooltip += TEXT(')');
|
|
};
|
|
for (const auto& ParamTokenPair : ParsedParamTokens)
|
|
{
|
|
PythonizedTooltip += TEXT('\n');
|
|
PythonizedTooltip += TEXT("param: ");
|
|
PythonizedTooltip += PythonizePropertyName(ParamTokenPair.Key.ToString(), EPythonizeNameCase::Lower);
|
|
|
|
if (InContext.ParamsStruct)
|
|
{
|
|
if (const UProperty* ParamProp = InContext.ParamsStruct->FindPropertyByName(ParamTokenPair.Key))
|
|
{
|
|
AppendParamTypeDoc(ParamProp);
|
|
}
|
|
}
|
|
|
|
if (!ParamTokenPair.Value.IsEmpty())
|
|
{
|
|
PythonizedTooltip += TEXT(" -- ");
|
|
PythonizedTooltip += ParamTokenPair.Value;
|
|
}
|
|
}
|
|
if (InContext.ParamsStruct)
|
|
{
|
|
for (TFieldIterator<const UProperty> ParamIt(InContext.ParamsStruct); ParamIt; ++ParamIt)
|
|
{
|
|
const UProperty* ParamProp = *ParamIt;
|
|
|
|
const bool bHasProcessedParamProp = ParsedParamTokens.ContainsByPredicate([&ParamProp](const TTuple<FName, FString>& ParamTokenPair)
|
|
{
|
|
return ParamTokenPair.Key == ParamProp->GetFName();
|
|
});
|
|
|
|
if (bHasProcessedParamProp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
PythonizedTooltip += TEXT('\n');
|
|
PythonizedTooltip += TEXT("param: ");
|
|
PythonizedTooltip += PythonizePropertyName(ParamProp->GetName(), EPythonizeNameCase::Lower);
|
|
|
|
AppendParamTypeDoc(ParamProp);
|
|
}
|
|
}
|
|
|
|
// Process the return token into PythonizedTooltip
|
|
if (!ReturnToken.IsEmpty())
|
|
{
|
|
PythonizedTooltip += TEXT('\n');
|
|
PythonizedTooltip += TEXT("return: ");
|
|
PythonizedTooltip += ReturnToken;
|
|
}
|
|
|
|
PythonizedTooltip.TrimEndInline();
|
|
|
|
return PythonizedTooltip;
|
|
}
|
|
|
|
FString GetFieldModule(const UField* InField)
|
|
{
|
|
// todo: should have meta-data on the type that can override this for scripting
|
|
UPackage* ScriptPackage = InField->GetOutermost();
|
|
|
|
const FString PackageName = ScriptPackage->GetName();
|
|
if (PackageName.StartsWith(TEXT("/Script/")))
|
|
{
|
|
return PackageName.RightChop(8); // Chop "/Script/" from the name
|
|
}
|
|
|
|
check(PackageName[0] == TEXT('/'));
|
|
int32 RootNameEnd = 1;
|
|
for (; PackageName[RootNameEnd] != TEXT('/'); ++RootNameEnd) {}
|
|
return PackageName.Mid(1, RootNameEnd - 1);
|
|
}
|
|
|
|
FString GetModulePythonName(const FName InModuleName, const bool bIncludePrefix)
|
|
{
|
|
// Some modules are mapped to others in Python
|
|
static const FName PythonModuleMappings[][2] = {
|
|
{ TEXT("CoreUObject"), TEXT("Core") },
|
|
{ TEXT("SlateCore"), TEXT("Slate") },
|
|
{ TEXT("UnrealEd"), TEXT("Editor") },
|
|
{ TEXT("PythonScriptPlugin"), TEXT("Python") },
|
|
};
|
|
|
|
FName MappedModuleName = InModuleName;
|
|
for (const auto& PythonModuleMapping : PythonModuleMappings)
|
|
{
|
|
if (InModuleName == PythonModuleMapping[0])
|
|
{
|
|
MappedModuleName = PythonModuleMapping[1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
const FString ModulePythonName = MappedModuleName.ToString().ToLower();
|
|
return bIncludePrefix ? FString::Printf(TEXT("_unreal_%s"), *ModulePythonName) : ModulePythonName;
|
|
}
|
|
|
|
FString GetClassPythonName(const UClass* InClass)
|
|
{
|
|
FString ClassName = InClass->GetMetaData(ScriptNameMetaDataKey);
|
|
if (ClassName.IsEmpty())
|
|
{
|
|
ClassName = InClass->GetName();
|
|
}
|
|
return ClassName;
|
|
}
|
|
|
|
FString GetStructPythonName(const UStruct* InStruct)
|
|
{
|
|
FString StructName = InStruct->GetMetaData(ScriptNameMetaDataKey);
|
|
if (StructName.IsEmpty())
|
|
{
|
|
StructName = InStruct->GetName();
|
|
}
|
|
return StructName;
|
|
}
|
|
|
|
FString GetEnumPythonName(const UEnum* InEnum)
|
|
{
|
|
FString EnumName = InEnum->UField::GetMetaData(ScriptNameMetaDataKey);
|
|
if (EnumName.IsEmpty())
|
|
{
|
|
EnumName = InEnum->GetName();
|
|
|
|
// Strip the "E" prefix from enum names
|
|
if (EnumName.Len() >= 2 && EnumName[0] == TEXT('E') && FChar::IsUpper(EnumName[1]))
|
|
{
|
|
EnumName.RemoveAt(0, 1, /*bAllowShrinking*/false);
|
|
}
|
|
}
|
|
return EnumName;
|
|
}
|
|
|
|
FString GetDelegatePythonName(const UFunction* InDelegateSignature)
|
|
{
|
|
return InDelegateSignature->GetName().LeftChop(19); // Trim the "__DelegateSignature" suffix from the name
|
|
}
|
|
|
|
FString GetFunctionPythonName(const UFunction* InFunc)
|
|
{
|
|
FString FuncName = InFunc->GetMetaData(ScriptNameMetaDataKey);
|
|
if (FuncName.IsEmpty())
|
|
{
|
|
FuncName = InFunc->GetName();
|
|
}
|
|
return PythonizeName(FuncName, EPythonizeNameCase::Lower);
|
|
}
|
|
|
|
FString GetPropertyPythonName(const UProperty* InProp)
|
|
{
|
|
FString PropName = InProp->GetMetaData(ScriptNameMetaDataKey);
|
|
if (PropName.IsEmpty())
|
|
{
|
|
PropName = InProp->GetName();
|
|
}
|
|
return PythonizePropertyName(PropName, EPythonizeNameCase::Lower);
|
|
}
|
|
|
|
FString GetPropertyPythonType(const UProperty* InProp, const bool bIncludeReadWriteState, const uint64 InReadOnlyFlags)
|
|
{
|
|
FString RetStr;
|
|
AppendPropertyPythonType(InProp, RetStr, bIncludeReadWriteState, InReadOnlyFlags);
|
|
return RetStr;
|
|
}
|
|
|
|
void AppendPropertyPythonType(const UProperty* InProp, FString& OutStr, const bool bIncludeReadWriteState, const uint64 InReadOnlyFlags)
|
|
{
|
|
FString PropExtendedType;
|
|
const FString PropType = InProp->GetCPPType(&PropExtendedType, CPPF_ArgumentOrReturnValue);
|
|
|
|
OutStr += PropType;
|
|
OutStr += PropExtendedType;
|
|
|
|
if (bIncludeReadWriteState)
|
|
{
|
|
OutStr += (InProp->HasAnyPropertyFlags(InReadOnlyFlags) ? TEXT(" [Read-Only]") : TEXT(" [Read-Write]"));
|
|
}
|
|
}
|
|
|
|
FString GetFieldTooltip(const UField* InField)
|
|
{
|
|
// We use the source string here as the culture may change while the editor is running, and also because some versions
|
|
// of Python (<3.4) can't override the default encoding to UTF-8 so produce errors when trying to print the help docs
|
|
return *FTextInspector::GetSourceString(InField->GetToolTipText());
|
|
}
|
|
|
|
FString GetEnumEntryTooltip(const UEnum* InEnum, const int64 InEntryIndex)
|
|
{
|
|
// We use the source string here as the culture may change while the editor is running, and also because some versions
|
|
// of Python (<3.4) can't override the default encoding to UTF-8 so produce errors when trying to print the help docs
|
|
return *FTextInspector::GetSourceString(InEnum->GetToolTipTextByIndex(InEntryIndex));
|
|
}
|
|
|
|
}
|
|
|
|
#endif // WITH_PYTHON
|