Overhaul of manual-generation code

This commit is contained in:
2026-04-13 21:33:02 -04:00
parent 1db2705877
commit 34011e43d5
8 changed files with 235 additions and 264 deletions

View File

@@ -12,6 +12,9 @@ class UWing_Documentation_Manual : public UWingHandler
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(EditAnywhere, meta=(Optional, Description="If specified, print only this section of the manual"))
FString Section;
virtual void Register() override virtual void Register() override
{ {
UWingServer::AddHandler(this, UWingServer::AddHandler(this,
@@ -19,6 +22,23 @@ public:
} }
virtual void Handle() override virtual void Handle() override
{ {
WingManual::PrintManual({WingManual::Section::All}, nullptr, false); if (Section.IsEmpty())
{
UWingManualSections::FetcherPaths();
UWingManualSections::ExpressingTypes();
UWingManualSections::VariableDeclarations();
UWingManualSections::EscapeSequencesInFNames();
UWingManualSections::MaterialEditing();
UWingManualSections::ImportantCommands();
}
else
{
FName SectionName(*Section);
if (!WingManual::PrintSection(SectionName))
{
WingOut::Stdout.Printf(TEXT("ERROR: Unknown manual section '%s'\nAvailable sections: "), *Section);
WingManual::PrintSectionNames(WingManual::GetSections());
}
}
} }
}; };

View File

@@ -53,7 +53,7 @@ void WingFetcher::PathFailed(const TCHAR* Expected)
Errors.Printf(TEXT("ERROR: Path specifies a %s, but expected %s\n"), *Obj.Get()->GetClass()->GetName(), Expected); Errors.Printf(TEXT("ERROR: Path specifies a %s, but expected %s\n"), *Obj.Get()->GetClass()->GetName(), Expected);
else else
Errors.Printf(TEXT("ERROR: Path led to a null pointer\n")); Errors.Printf(TEXT("ERROR: Path led to a null pointer\n"));
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
SetError(); SetError();
} }
@@ -63,7 +63,7 @@ WingFetcher& WingFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expecte
Errors.Printf(TEXT("ERROR: Input to '%s' is %s, but expected %s\n"), Walker, *Obj.Get()->GetClass()->GetName(), Expected); Errors.Printf(TEXT("ERROR: Input to '%s' is %s, but expected %s\n"), Walker, *Obj.Get()->GetClass()->GetName(), Expected);
else else
Errors.Printf(TEXT("ERROR: Path led to a null pointer\n")); Errors.Printf(TEXT("ERROR: Path led to a null pointer\n"));
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
SetError(); SetError();
return *this; return *this;
} }
@@ -75,8 +75,8 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (Path.Contains(TEXT(" "))) if (Path.Contains(TEXT(" ")))
{ {
Errors.Printf(TEXT("ERROR: Paths may not contain whitespace.")); Errors.Printf(TEXT("ERROR: Paths may not contain whitespace."));
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
UWingServer::SuggestManual(WingManual::Section::EscapeSequences); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
return SetError(); return SetError();
} }
TArray<FString> Segments; TArray<FString> Segments;
@@ -84,7 +84,7 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (Segments.Num() == 0) if (Segments.Num() == 0)
{ {
Errors.Print(TEXT("ERROR: Empty path\n")); Errors.Print(TEXT("ERROR: Empty path\n"));
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
return SetError(); return SetError();
} }
@@ -105,7 +105,7 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (!Func) if (!Func)
{ {
Errors.Printf(TEXT("ERROR: Unknown path step '%s'\n"), *Key); Errors.Printf(TEXT("ERROR: Unknown path step '%s'\n"), *Key);
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
return SetError(); return SetError();
} }
(this->*Func)(Value); (this->*Func)(Value);
@@ -122,7 +122,7 @@ WingFetcher& WingFetcher::Asset(const FString& PackagePath)
if (!PackagePath.StartsWith(TEXT("/"))) if (!PackagePath.StartsWith(TEXT("/")))
{ {
Errors.Printf(TEXT("ERROR: Path must start with '/', got '%s'\n"), *PackagePath); Errors.Printf(TEXT("ERROR: Path must start with '/', got '%s'\n"), *PackagePath);
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
return SetError(); return SetError();
} }
@@ -182,7 +182,7 @@ WingFetcher& WingFetcher::Graph(const FString& Value)
if (!Value.IsEmpty()) if (!Value.IsEmpty())
{ {
Errors.Printf(TEXT("ERROR: Materials have only one graph, with a blank name.\n\n")); Errors.Printf(TEXT("ERROR: Materials have only one graph, with a blank name.\n\n"));
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
return SetError(); return SetError();
} }
WingUtils::EnsureMaterialGraph(Mat); WingUtils::EnsureMaterialGraph(Mat);

View File

@@ -58,42 +58,14 @@ void WingManual::PrintHandlerHelp(const FWingHandlerConfig& Handler)
WingOut::Stdout.Print(TEXT("\n")); WingOut::Stdout.Print(TEXT("\n"));
} }
void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* Handler, bool Abridged) void UWingManualSections::FetcherPaths()
{
if (Sections.IsEmpty()) return;
const bool bPrintAll = Sections.Contains(Section::All);
if (Abridged)
{
WingOut::Stdout.Printf(TEXT("\n--- AUTOMATIC DOCUMENTATION ---\n"));
}
if (Handler && (Sections.Contains(Section::HandlerHelp) || bPrintAll))
{
PrintHandlerHelp(*Handler);
}
if (Sections.Contains(Section::Paths) || bPrintAll)
{
if (Abridged)
{ {
WingOut::Stdout.Print(TEXT( WingOut::Stdout.Print(TEXT(
"\n PATHS: Here are some example paths:" "\n FETCHER PATHS:"
"\n /Game/Widgets/WB_Hotkeys,widget:Canvas·122"
"\n /Game/Testing/BP_Test,graph:Rescale·Actor,node:K2Node_CallFunction_0,pin:Scale"
"\n /Game/Chars/BP_Manny,component:Camera·Boom"
"\n" "\n"
)); "\n Most commands require you to specify a 'fetcher path'."
} "\n A fetcher path starts with an asset name, followed by"
else "\n steps that navigate into the asset. Some Examples:"
{
WingOut::Stdout.Print(TEXT(
"\n PATHS:"
"\n"
"\n Most commands require you to specify a path. A path starts"
"\n with an asset name, followed by steps separated by ,"
"\n that navigate into the asset. Some Examples:"
"\n" "\n"
"\n /Game/Widgets/WB_Hotkeys,widget:Canvas.122" "\n /Game/Widgets/WB_Hotkeys,widget:Canvas.122"
"\n /Game/Testing/BP_Test,graph:Rescale.Actor,node:K2Node_CallFunction_0,pin:Scale" "\n /Game/Testing/BP_Test,graph:Rescale.Actor,node:K2Node_CallFunction_0,pin:Scale"
@@ -107,9 +79,10 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\n component — move from a blueprint to a component" "\n component — move from a blueprint to a component"
"\n levelblueprint — move from a world to a blueprint" "\n levelblueprint — move from a world to a blueprint"
"\n widget — move from a widget blueprint to a widget" "\n widget — move from a widget blueprint to a widget"
"\n structprop — move into a struct property of an object"
"\n" "\n"
"\n Notice that paths use sanitized identifiers. See the section" "\n Notice that paths use escaped fnames. See the section"
"\n on identifier sanitization below for more information." "\n on escape sequences in fnames below sfor more information."
"\n" "\n"
"\n Steps do not always require a parameter. For example, materials" "\n Steps do not always require a parameter. For example, materials"
"\n only have one graph, so you can just say:" "\n only have one graph, so you can just say:"
@@ -118,28 +91,14 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\n" "\n"
)); ));
} }
}
if (Sections.Contains(Section::Types) || bPrintAll) void UWingManualSections::ExpressingTypes()
{
if (Abridged)
{ {
WingOut::Stdout.Print(TEXT( WingOut::Stdout.Print(TEXT(
"\n TYPES: Here are some examples of valid types:" "\n EXPRESSING TYPES:"
"\n Bool, String, Vector, Rotator, HitResult, Actor, Character,"
"\n PlayerController, EBlendMode, EMovementMode, BP_Manny, BP_Quinn,"
"\n Array<Int>, Set<String>, Map<Int,Actor>"
"\n Soft<ABP_Manny>, Class<Pawn>, SoftClass<Pawn>"
"\n" "\n"
)); "\n To change the type of a variable, or to express function parameters,"
} "\n you will use our syntax for types. Here are some valid examples:"
else
{
WingOut::Stdout.Print(TEXT(
"\n TYPES:"
"\n"
"\n To change variable types, or to express function prototypes, you will"
"\n use our syntax for types. Here are some valid examples:"
"\n" "\n"
"\n Bool, String, Vector, Rotator, HitResult, Actor, Character," "\n Bool, String, Vector, Rotator, HitResult, Actor, Character,"
"\n PlayerController, EBlendMode, EMovementMode, BP_Manny, BP_Quinn," "\n PlayerController, EBlendMode, EMovementMode, BP_Manny, BP_Quinn,"
@@ -148,24 +107,13 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\n" "\n"
"\n Notice that it's 'Actor', not 'AActor'. Type names are not" "\n Notice that it's 'Actor', not 'AActor'. Type names are not"
"\n case-sensitive. When a blueprint like /Game/Testing/BP_Foo" "\n case-sensitive. When a blueprint like /Game/Testing/BP_Foo"
"\n is used as a type, the typename is BP_Foo." "\n is used as a type, the typename is just BP_Foo. You can search"
"\n for valid types using the TypeName_Search command."
"\n" "\n"
)); ));
} }
}
if (Sections.Contains(Section::VariableDeclarations) || bPrintAll) void UWingManualSections::VariableDeclarations()
{
if (Abridged)
{
WingOut::Stdout.Print(TEXT(
"\n VARIABLE DECLARATIONS: example variable declarations:"
"\n Array<Actor> Actors"
"\n Float F (InstanceEditable)"
"\n String S = This is the default value"
));
}
else
{ {
WingOut::Stdout.Print(TEXT( WingOut::Stdout.Print(TEXT(
"\n VARIABLE DECLARATIONS:" "\n VARIABLE DECLARATIONS:"
@@ -179,7 +127,7 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\n String S = This is the default value" "\n String S = This is the default value"
"\n" "\n"
"\n The commands Variables_Add, Variables_Modify," "\n The commands Variables_Add, Variables_Modify,"
"\n and Variables_Remove can be used to edit: " "\n and Variables_Remove can be used to edit "
"\n blueprint variables, graph local variables, graph input" "\n blueprint variables, graph local variables, graph input"
"\n variables, graph output variables, and custom" "\n variables, graph output variables, and custom"
"\n event node input variables. Event dispatchers are" "\n event node input variables. Event dispatchers are"
@@ -187,40 +135,12 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\n" "\n"
)); ));
} }
}
if (Sections.Contains(Section::EscapeSequences) || bPrintAll) void UWingManualSections::EscapeSequencesInFNames()
{
if (Abridged)
{ {
WingOut::Stdout.Print(TEXT( WingOut::Stdout.Print(TEXT(
"\n USING HTML ESCAPE SEQUENCES:" "\n ESCAPE SEQUENCES IN FNAMES:"
"\n When we output FNames, we use HTML escape sequences for the"
"\n following marks: \\\"'(),.:;<=>& We also escape control"
"\n characters, and some (but not all) unicode characters."
"\n We also translate spaces to periods. Examples:"
"\n" "\n"
"\n FName(TEXT(\"No_Change\")) --> No_Change"
"\n FName(TEXT(\"ίδιος\")) --> ίδιος"
"\n FName(TEXT(\"✡✢❄\")) --> &#10017;&#10018;&#10052;"
"\n FName(TEXT(\"Hello.There\")) --> Hello&period;There"
"\n FName(TEXT(\"Hello There\")) --> Hello.There"
"\n FName(TEXT(\"Hello\n\")) --> Hello&NewLine;"
"\n "
"\n When sending FNames to UE Wingman, you *must* escape the marks"
"\n listed above and control characters, but you *may* escape"
"\n any character. To send an FName with a space in it, either"
"\n use &#32; or a period."
"\n"
"\n Currently, this escaping *only* applies to FNames. It"
"\n doesn't work to use escapes in asset names or file names."
"\n"
));
}
else
{
WingOut::Stdout.Print(TEXT(
"\n USING HTML ESCAPE SEQUENCES:"
"\n When we output FNames, we use HTML escape sequences for the" "\n When we output FNames, we use HTML escape sequences for the"
"\n following marks: \\\"'(),.:;<=>&, and for certain other characters." "\n following marks: \\\"'(),.:;<=>&, and for certain other characters."
"\n We also translate spaces to periods." "\n We also translate spaces to periods."
@@ -231,32 +151,8 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\n" "\n"
)); ));
} }
}
if (Sections.Contains(Section::Whitespace) || bPrintAll) void UWingManualSections::MaterialEditing()
{
WingOut::Stdout.Print(TEXT(
"\n ABOUT WHITESPACE:"
"\n Do not put excess whitespace into paths, typenames, or"
"\n function prototypes, only use whitespace where it is required"
"\n by the syntax."
"\n"
));
}
if (Sections.Contains(Section::MaterialEditing) || bPrintAll)
{
if (Abridged)
{
WingOut::Stdout.Print(TEXT(
"\n MATERIAL EDITING:"
"\n We do not expose material expressions directly. Instead, use"
"\n Details_Dump and Details_Set on the material graph nodes to"
"\n edit material expression properties."
"\n"
));
}
else
{ {
WingOut::Stdout.Print(TEXT( WingOut::Stdout.Print(TEXT(
"\n MATERIAL EDITING:" "\n MATERIAL EDITING:"
@@ -267,15 +163,18 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\n properties which actually come from the material expressions." "\n properties which actually come from the material expressions."
"\n You can edit these using Details_Set on the node." "\n You can edit these using Details_Set on the node."
"\n" "\n"
"\n Don't overlook custom HLSL nodes. These can accomplish in\n"
"\n a single node what would otherwise take many.\n"
"\n"
)); ));
} }
}
if (Sections.Contains(Section::ImportantCommands) || bPrintAll) void UWingManualSections::ImportantCommands()
{ {
WingOut::Stdout.Print(TEXT( WingOut::Stdout.Print(TEXT(
"\n COMMANDS YOU SHOULD KNOW ABOUT AND REMEMBER:" "\n IMPORTANT COMMANDS:"
"\n Documentation_Manual: this explanation" "\n"
"\n Documentation_Manual: print manual sections"
"\n Documentation_Commands: a list of all the main commands" "\n Documentation_Commands: a list of all the main commands"
"\n Documentation_CreateAssets: Additional commands that create new assets" "\n Documentation_CreateAssets: Additional commands that create new assets"
"\n Blueprint_Dump: a summary of any blueprint" "\n Blueprint_Dump: a summary of any blueprint"
@@ -283,16 +182,44 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\n Details_Dump: Dump the details panel for a given object" "\n Details_Dump: Dump the details panel for a given object"
"\n Details_Set: Manipulate the details panel for a given object" "\n Details_Set: Manipulate the details panel for a given object"
"\n" "\n"
"\n You can use Documentation_Commands(Query=SomeCommand,Verbose=true)" "\n You can use Documentation_Commands(Query=Command,Verbose=true)"
"\n to get detailed help for a specific command." "\n to get detailed help for a specific command."
"\n" "\n"
"\n You can use Documentation_Manual(Section=Name) to print out"
"\n a specific manual section."
"\n"
)); ));
} }
if (Abridged) TSet<FName> WingManual::GetSections()
{ {
WingOut::Stdout.Printf(TEXT("\nUse command 'Documentation_Manual' to see the full manual.\n")); TSet<FName> Result;
for (TFieldIterator<UFunction> It(UWingManualSections::StaticClass(), EFieldIterationFlags::None); It; ++It)
{
Result.Add(It->GetFName());
} }
return Result;
}
void WingManual::PrintSectionNames(const TSet<FName>& Sections)
{
if (Sections.IsEmpty()) return;
bool bFirst = true;
for (const FName& Section : Sections)
{
if (!bFirst) WingOut::Stdout.Print(TEXT(", "));
bFirst = false;
WingOut::Stdout.Printf(TEXT("%s"), *Section.ToString());
}
WingOut::Stdout.Print(TEXT("\n"));
}
bool WingManual::PrintSection(FName Section)
{
UFunction* Func = UWingManualSections::StaticClass()->FindFunctionByName(Section);
if (!Func) return false;
UWingManualSections::StaticClass()->GetDefaultObject()->ProcessEvent(Func, nullptr);
return true;
} }
void WingManual::Commands(EWingHandlerKind Kind, const FString& Query, bool Verbose) void WingManual::Commands(EWingHandlerKind Kind, const FString& Query, bool Verbose)

View File

@@ -175,6 +175,7 @@ FString UWingServer::HandleRequest(const FString& Line)
LogCapture.bEnabled = true; LogCapture.bEnabled = true;
WingOut::StdoutBuffer.Reset(); WingOut::StdoutBuffer.Reset();
SuggestedManualSections.Empty(); SuggestedManualSections.Empty();
bSuggestHandlerHelp = false;
LastHandler = nullptr; LastHandler = nullptr;
TryCallHandler(Line); TryCallHandler(Line);
@@ -186,10 +187,14 @@ FString UWingServer::HandleRequest(const FString& Line)
WingOut::Stdout.Printf(TEXT("UE_LOG: %s\n"), *Msg); WingOut::Stdout.Printf(TEXT("UE_LOG: %s\n"), *Msg);
} }
LogCapture.CapturedErrors.Empty(); LogCapture.CapturedErrors.Empty();
if (bSuggestHandlerHelp && LastHandler)
{
WingManual::PrintHandlerHelp(*LastHandler);
}
if (!SuggestedManualSections.IsEmpty()) if (!SuggestedManualSections.IsEmpty())
{ {
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); WingOut::Stdout.Print(TEXT("Suggested manual sections: "));
WingManual::PrintManual(SuggestedManualSections, LastHandler, true); WingManual::PrintSectionNames(SuggestedManualSections);
} }
FString Result = WingOut::StdoutBuffer.ToString(); FString Result = WingOut::StdoutBuffer.ToString();
WingOut::StdoutBuffer.Reset(); WingOut::StdoutBuffer.Reset();
@@ -227,7 +232,7 @@ void UWingServer::TryCallHandler(const FString &Line)
if (!Found) if (!Found)
{ {
WingOut::Stdout.Printf(TEXT("Unknown command: %s"), *Command); WingOut::Stdout.Printf(TEXT("Unknown command: %s"), *Command);
UWingServer::SuggestManual(WingManual::Section::ImportantCommands); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, ImportantCommands));
return; return;
} }
LastHandler = Found; LastHandler = Found;
@@ -241,7 +246,7 @@ void UWingServer::TryCallHandler(const FString &Line)
TArray<FWingProperty> Props = FWingProperty::GetVisible(Handler); TArray<FWingProperty> Props = FWingProperty::GetVisible(Handler);
if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout)) if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout))
{ {
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
return; return;
} }

View File

@@ -430,7 +430,7 @@ void UWingTypes::PrintParseError(WingTokenizer& Tok, const TCHAR* Message, WingO
{ {
FString TypeText(Tok.GetRange(NAME_StartOfType, 1)); FString TypeText(Tok.GetRange(NAME_StartOfType, 1));
Errors.Printf(TEXT("ERROR parsing type '%s' — %s\n"), *TypeText, Message); Errors.Printf(TEXT("ERROR parsing type '%s' — %s\n"), *TypeText, Message);
UWingServer::SuggestManual(WingManual::Section::Types); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, ExpressingTypes));
} }
@@ -626,7 +626,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
if (IsNone && OutPinType.IsContainer()) if (IsNone && OutPinType.IsContainer())
{ {
Errors.Printf(TEXT("ERROR: 'None' is not allowed in an array/set/map\n")); Errors.Printf(TEXT("ERROR: 'None' is not allowed in an array/set/map\n"));
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
@@ -634,7 +634,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
if (!Require.AllowNone && IsNone) if (!Require.AllowNone && IsNone)
{ {
Errors.Printf(TEXT("ERROR: 'None' is not allowed here\n")); Errors.Printf(TEXT("ERROR: 'None' is not allowed here\n"));
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
@@ -644,7 +644,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{ {
FString Text(Tok.GetRange(NAME_StartOfType, 0)); FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: Type '%s' is a container, not allowed here\n"), *Text); Errors.Printf(TEXT("ERROR: Type '%s' is a container, not allowed here\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
} }
@@ -655,7 +655,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{ {
Errors.Printf(TEXT("ERROR: Need a type which is an %s, got a %s instead.\n"), Errors.Printf(TEXT("ERROR: Need a type which is an %s, got a %s instead.\n"),
*Require.PinCategory.ToString(), *OutPinType.PinCategory.ToString()); *Require.PinCategory.ToString(), *OutPinType.PinCategory.ToString());
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
} }
@@ -668,13 +668,13 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{ {
FString Text(Tok.GetRange(NAME_StartOfType, 0)); FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: '%s' is an interface, not a class\n"), *Text); Errors.Printf(TEXT("ERROR: '%s' is an interface, not a class\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
if (!IsChildOf(OutPinType, Require.IsChildOf)) if (!IsChildOf(OutPinType, Require.IsChildOf))
{ {
Errors.Printf(TEXT("ERROR: Type must derive from %s\n"), *WingUtils::FormatName(Require.IsChildOf)); Errors.Printf(TEXT("ERROR: Type must derive from %s\n"), *WingUtils::FormatName(Require.IsChildOf));
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
} }
@@ -685,7 +685,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{ {
FString Text(Tok.GetRange(NAME_StartOfType, 0)); FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: Not a blueprint type: %s\n"), *Text); Errors.Printf(TEXT("ERROR: Not a blueprint type: %s\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
} }
@@ -696,7 +696,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{ {
FString Text(Tok.GetRange(NAME_StartOfType, 0)); FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: Not a blueprintable type: %s\n"), *Text); Errors.Printf(TEXT("ERROR: Not a blueprintable type: %s\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
} }

View File

@@ -60,7 +60,7 @@ FName WingUtils::CheckInternalizeID(const FString &ExternalID, WingOut Errors)
if (!Error.IsEmpty()) if (!Error.IsEmpty())
{ {
Errors.Printf(TEXT("%s\n"), *Error); Errors.Printf(TEXT("%s\n"), *Error);
UWingServer::SuggestManual(WingManual::Section::EscapeSequences); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
} }
return InternalID; return InternalID;
} }
@@ -77,7 +77,7 @@ FName WingUtils::CheckProposedName(const FString &ExternalID, const TSet<FName>
{ {
Errors.Printf(TEXT("ERROR: id %s would not be a readable id, may not create item with this name"), Errors.Printf(TEXT("ERROR: id %s would not be a readable id, may not create item with this name"),
*ExternalID); *ExternalID);
UWingServer::SuggestManual(WingManual::Section::EscapeSequences); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
return FName(); return FName();
} }
if (InUse.Contains(InternalID)) if (InUse.Contains(InternalID))

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "WingManual.generated.h"
struct FWingHandlerConfig; struct FWingHandlerConfig;
enum class EWingHandlerKind; enum class EWingHandlerKind;
@@ -7,23 +8,37 @@ enum class EWingHandlerKind;
class WingManual class WingManual
{ {
public: public:
enum class Section
{
All,
HandlerHelp,
Paths,
Types,
VariableDeclarations,
EscapeSequences,
Whitespace,
MaterialEditing,
ImportantCommands,
};
static void PrintHandlerPrototype(const FWingHandlerConfig& Handler); static void PrintHandlerPrototype(const FWingHandlerConfig& Handler);
static void PrintHandlerArguments(const FWingHandlerConfig& Handler); static void PrintHandlerArguments(const FWingHandlerConfig& Handler);
static void PrintHandlerDescription(const FWingHandlerConfig& Handler); static void PrintHandlerDescription(const FWingHandlerConfig& Handler);
static void PrintHandlerHelp(const FWingHandlerConfig& Handler); static void PrintHandlerHelp(const FWingHandlerConfig& Handler);
static void PrintManual(TSet<Section> Sections, const FWingHandlerConfig* Handler, bool Abridged);
static void Commands(EWingHandlerKind Kind, const FString& Query, bool Verbose); static void Commands(EWingHandlerKind Kind, const FString& Query, bool Verbose);
static TSet<FName> GetSections();
static bool PrintSection(FName Section);
static void PrintSectionNames(const TSet<FName>& Sections);
};
UCLASS()
class UWingManualSections : public UObject
{
GENERATED_BODY()
public:
UFUNCTION()
static void FetcherPaths();
UFUNCTION()
static void ExpressingTypes();
UFUNCTION()
static void VariableDeclarations();
UFUNCTION()
static void EscapeSequencesInFNames();
UFUNCTION()
static void MaterialEditing();
UFUNCTION()
static void ImportantCommands();
}; };

View File

@@ -48,7 +48,10 @@ public:
static void AddTouchedObject(UObject* Obj) { GWingServer->Notifier.AddTouchedObject(Obj); } static void AddTouchedObject(UObject* Obj) { GWingServer->Notifier.AddTouchedObject(Obj); }
/** Suggest that a manual section be printed after the handler finishes. */ /** Suggest that a manual section be printed after the handler finishes. */
static void SuggestManual(WingManual::Section Section) { GWingServer->SuggestedManualSections.Add(Section); } static void SuggestManual(FName Section) { GWingServer->SuggestedManualSections.Add(Section); }
/** Suggest that handler help be printed after the handler finishes. */
static void SuggestHandlerHelp() { GWingServer->bSuggestHandlerHelp = true; }
/** Port the server is listening on. */ /** Port the server is listening on. */
int32 GetPort() const { return Port; } int32 GetPort() const { return Port; }
@@ -64,7 +67,8 @@ private:
UPROPERTY() UPROPERTY()
FWingNotifier Notifier; FWingNotifier Notifier;
FWingHandlerConfig* LastHandler; FWingHandlerConfig* LastHandler;
TSet<WingManual::Section> SuggestedManualSections; bool bSuggestHandlerHelp;
TSet<FName> SuggestedManualSections;
FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request
TArray<FWingHandlerConfig> WingHandlerRegistry; // sorted by Name TArray<FWingHandlerConfig> WingHandlerRegistry; // sorted by Name
void BuildWingHandlerRegistry(); void BuildWingHandlerRegistry();