cinera: Upgrade to hmmlib2

Features:
    User-configurable roles and credits
    Handle SIGINT to quit cleanly and avoid database corruption

Fixes:
    Filesystem event monitoring handles directory creation / deletion
    Fixed buffer overflow when trying to curl in a non-existent quote
This commit is contained in:
Matt Mascarenhas 2021-05-30 19:39:47 +01:00
parent 65fe93fb57
commit 0d88c24db6
2 changed files with 1580 additions and 348 deletions

File diff suppressed because it is too large Load Diff

View File

@ -480,10 +480,26 @@ typedef struct
{
string ID;
string Name;
// TODO(matt): string SortName;
string QuoteUsername;
string Homepage;
_memory_book(support) Support;
} person;
typedef struct
{
string ID;
string Name;
string Plural;
bool NonSpeaking;
} role;
typedef struct
{
person *Person;
role *Role;
} credit;
typedef struct project
{
string ID;
@ -515,7 +531,10 @@ typedef struct project
uint64_t IconVariants;
asset *IconAsset;
#if(HMMLIB_MAJOR_VERSION == 2)
#else
string StreamUsername;
#endif
string VODPlatform; // TODO(matt): Make this an enum
bool DenyBespokeTemplates;
@ -523,9 +542,13 @@ typedef struct project
bool IgnorePrivacy;
person *Owner;
_memory_book(credit *) Credit;
#if(HMMLIB_MAJOR_VERSION == 2)
#else
_memory_book(person *) Indexer;
_memory_book(person *) Guest;
_memory_book(person *) CoHost;
#endif
_memory_book(medium) Medium;
medium *DefaultMedium;
@ -567,6 +590,7 @@ typedef struct
uint8_t LogLevel;
_memory_book(person) Person;
_memory_book(role) Role;
_memory_book(project) Project;
memory_book ResolvedVariables;
} config;
@ -669,6 +693,13 @@ Tokenise(memory_book *TokensList, string Path)
}
Advancement = 1;
}
else if(!StringsDifferS(TokenStrings[TOKEN_MINUS], B))
{
T.Type = TOKEN_MINUS;
T.Content.Base = B->Ptr;
++T.Content.Length;
Advancement = 1;
}
else if(!StringsDifferS(TokenStrings[TOKEN_ASSIGN], B))
{
T.Type = TOKEN_ASSIGN;
@ -747,7 +778,6 @@ Tokenise(memory_book *TokensList, string Path)
Advance(B, Advancement);
SkipWhitespace(Result, B);
}
// TODO(matt): PushConfigWatchHandle()
}
else
{
@ -893,7 +923,10 @@ InitTypeSpecs(void)
PushTypeSpecField(Root, FT_STRING, IDENT_SEARCH_LOCATION, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_SEARCH_TEMPLATE, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_TEMPLATES_DIR, TRUE);
#if(HMMLIB_MAJOR_VERSION == 2)
#else
PushTypeSpecField(Root, FT_STRING, IDENT_STREAM_USERNAME, TRUE);
#endif
PushTypeSpecField(Root, FT_STRING, IDENT_VOD_PLATFORM, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_THEME, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_TITLE, TRUE);
@ -916,6 +949,7 @@ InitTypeSpecs(void)
PushTypeSpecField(Root, FT_SCOPE, IDENT_INCLUDE, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_MEDIUM, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_PERSON, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_ROLE, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_PROJECT, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_SUPPORT, FALSE);
@ -934,8 +968,22 @@ InitTypeSpecs(void)
config_type_spec *Person = PushTypeSpec(&Result, IDENT_PERSON, FALSE);
PushTypeSpecField(Person, FT_STRING, IDENT_NAME, TRUE);
PushTypeSpecField(Person, FT_STRING, IDENT_HOMEPAGE, TRUE);
#if(HMMLIB_MAJOR_VERSION == 2)
PushTypeSpecField(Person, FT_STRING, IDENT_QUOTE_USERNAME, TRUE);
#endif
PushTypeSpecField(Person, FT_SCOPE, IDENT_SUPPORT, FALSE);
config_type_spec *Role = PushTypeSpec(&Result, IDENT_ROLE, FALSE);
PushTypeSpecField(Role, FT_STRING, IDENT_NAME, TRUE);
PushTypeSpecField(Role, FT_STRING, IDENT_PLURAL, TRUE);
PushTypeSpecField(Role, FT_NUMBER, IDENT_POSITION, TRUE);
PushTypeSpecField(Role, FT_BOOLEAN, IDENT_NON_SPEAKING, TRUE);
#if(HMMLIB_MAJOR_VERSION == 2)
config_type_spec *Credit = PushTypeSpec(&Result, IDENT_CREDIT, FALSE);
PushTypeSpecField(Credit, FT_STRING, IDENT_ROLE, FALSE);
#endif
config_type_spec *Medium = PushTypeSpec(&Result, IDENT_MEDIUM, FALSE);
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON, TRUE);
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_NORMAL, TRUE);
@ -949,21 +997,33 @@ InitTypeSpecs(void)
config_type_spec *Project = PushTypeSpec(&Result, IDENT_PROJECT, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_BASE_DIR, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_BASE_URL, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_COHOST, FALSE);
#if(HMMLIB_MAJOR_VERSION == 2)
#else
PushTypeSpecField(Project, FT_STRING, IDENT_COHOST, FALSE); // TODO(matt): Remove
#endif
PushTypeSpecField(Project, FT_STRING, IDENT_DEFAULT_MEDIUM, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_GENRE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_GUEST, FALSE);
#if(HMMLIB_MAJOR_VERSION == 2)
#else
PushTypeSpecField(Project, FT_STRING, IDENT_GUEST, FALSE); // TODO(matt): Remove
#endif
PushTypeSpecField(Project, FT_STRING, IDENT_HMML_DIR, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_INDEXER, FALSE);
#if(HMMLIB_MAJOR_VERSION == 2)
#else
PushTypeSpecField(Project, FT_STRING, IDENT_INDEXER, FALSE); // TODO(matt): Remove
#endif
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_SCHEME, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_OWNER, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_OWNER, TRUE); // NOTE(matt): Do not remove, because ResolveLocalVariable() recognises it
PushTypeSpecField(Project, FT_STRING, IDENT_PLAYER_LOCATION, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_PLAYER_TEMPLATE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_QUERY_STRING, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_SEARCH_LOCATION, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_SEARCH_TEMPLATE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_TEMPLATES_DIR, TRUE);
#if(HMMLIB_MAJOR_VERSION == 2)
#else
PushTypeSpecField(Project, FT_STRING, IDENT_STREAM_USERNAME, TRUE);
#endif
PushTypeSpecField(Project, FT_STRING, IDENT_VOD_PLATFORM, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_THEME, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE, TRUE);
@ -995,6 +1055,9 @@ InitTypeSpecs(void)
PushTypeSpecField(Project, FT_BOOLEAN, IDENT_SINGLE_BROWSER_TAB, TRUE);
//
#if(HMMLIB_MAJOR_VERSION == 2)
PushTypeSpecField(Project, FT_SCOPE, IDENT_CREDIT, FALSE);
#endif
PushTypeSpecField(Project, FT_SCOPE, IDENT_INCLUDE, FALSE);
PushTypeSpecField(Project, FT_SCOPE, IDENT_MEDIUM, FALSE);
PushTypeSpecField(Project, FT_SCOPE, IDENT_PROJECT, FALSE);
@ -1045,6 +1108,30 @@ PrintTypeField(config_type_field *F, config_identifier_id ParentScopeID, int Ind
--IndentationLevel;
}
}
else if((F->ID == IDENT_NAME) && ParentScopeID == IDENT_ROLE)
{
if(!ConfigIdentifiers[F->ID].IdentifierDescription_RoleDisplayed)
{
++IndentationLevel;
IndentedCarriageReturn(IndentationLevel);
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[F->ID].IdentifierDescription_Role));
ConfigIdentifiers[F->ID].IdentifierDescription_RoleDisplayed = TRUE;
--IndentationLevel;
}
}
#if(HMMLIB_MAJOR_VERSION == 2)
else if((F->ID == IDENT_ROLE) && ParentScopeID == IDENT_CREDIT)
{
if(!ConfigIdentifiers[F->ID].IdentifierDescription_CreditDisplayed)
{
++IndentationLevel;
IndentedCarriageReturn(IndentationLevel);
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[F->ID].IdentifierDescription_Credit));
ConfigIdentifiers[F->ID].IdentifierDescription_CreditDisplayed = TRUE;
--IndentationLevel;
}
}
#endif
else
{
if(!ConfigIdentifiers[F->ID].IdentifierDescriptionDisplayed)
@ -1127,7 +1214,7 @@ typedef struct
typedef struct
{
config_identifier_id Key;
uint64_t Value;
int64_t Value;
token_position Position;
} config_int_pair;
@ -1438,7 +1525,7 @@ PrintIntPair(config_int_pair *P, char *Delimiter, bool FillSyntax, uint64_t Inde
default:
{
Colourise(CS_BLUE_BOLD);
fprintf(stderr, "%lu", P->Value);
fprintf(stderr, "%li", P->Value);
} break;
}
Colourise(CS_END);
@ -1523,6 +1610,22 @@ PushPair(scope_tree *Parent, config_pair *P)
*New = *P;
}
config_pair *
GetPair(scope_tree *Parent, config_identifier_id FieldID)
{
config_pair *Result = 0;
for(int i = 0; i < Parent->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&Parent->Pairs, i);
if(This->Key == FieldID)
{
Result = This;
break;
}
}
return Result;
}
void
PushIntPair(scope_tree *Parent, config_int_pair *I)
{
@ -1530,6 +1633,22 @@ PushIntPair(scope_tree *Parent, config_int_pair *I)
*New = *I;
}
config_int_pair *
GetIntPair(scope_tree *Parent, config_identifier_id FieldID)
{
config_int_pair *Result = 0;
for(int i = 0; i < Parent->IntPairs.ItemCount; ++i)
{
config_int_pair *This = GetPlaceInBook(&Parent->IntPairs, i);
if(This->Key == FieldID)
{
Result = This;
break;
}
}
return Result;
}
void
PushBoolPair(scope_tree *Parent, config_bool_pair *B)
{
@ -2020,6 +2139,13 @@ PushDefaultIntPair(scope_tree *Parent, config_identifier_id Key, uint64_t Value)
return PushIntPair(Parent, &IntPair);
}
void
PushDefaultBoolPair(scope_tree *Parent, config_identifier_id Key, bool Value)
{
config_bool_pair BoolPair = { .Key = Key, .Value = Value };
return PushBoolPair(Parent, &BoolPair);
}
#define DEFAULT_PRIVACY_CHECK_INTERVAL 60 * 4
void
SetDefaults(scope_tree *Root, memory_book *TypeSpecs)
@ -2037,6 +2163,11 @@ SetDefaults(scope_tree *Root, memory_book *TypeSpecs)
PushDefaultPair(MediumAuthored, IDENT_ICON, Wrap0("&#128490;"));
PushDefaultPair(MediumAuthored, IDENT_NAME, Wrap0("Chat Comment"));
scope_tree *RoleIndexer = PushDefaultScope(TypeSpecs, Root, IDENT_ROLE, Wrap0("indexer"));
PushDefaultPair(RoleIndexer, IDENT_NAME, Wrap0("Indexer"));
PushDefaultBoolPair(RoleIndexer, IDENT_NON_SPEAKING, TRUE);
PushDefaultIntPair(RoleIndexer, IDENT_POSITION, -1);
PushDefaultPair(Root, IDENT_QUERY_STRING, Wrap0("r"));
PushDefaultPair(Root, IDENT_THEME, Wrap0("$origin"));
PushDefaultPair(Root, IDENT_CACHE_DIR, Wrap0("$XDG_CACHE_HOME/cinera"));
@ -2244,8 +2375,14 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeScopeTree(Tree); return 0; }
token Value = {};
bool IsNegative = FALSE;
if(TokenIs(T, TOKEN_MINUS))
{
IsNegative = TRUE;
++T->CurrentIndex;
}
if(!ExpectToken(T, TOKEN_NUMBER, &Value)) { FreeScopeTree(Tree); return 0; }
IntPair.Value = Value.int64_t;
IntPair.Value = IsNegative ? 0 - Value.int64_t : Value.int64_t;
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
@ -2320,16 +2457,12 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T
ConfigError(&IncluderFilePath, Pair.Position.LineNumber, S_WARNING, "Unable to include file: ", &Pair.Value);
}
} break;
case RC_SCHEME_MIXTURE:
{
} break;
case RC_SYNTAX_ERROR:
{
FreeScopeTree(Tree); return 0;
} break;
case RC_SCHEME_MIXTURE:
case RC_INVALID_IDENTIFIER:
{
} break;
default: break;
}
}
@ -2550,25 +2683,66 @@ ResolveEnvironmentVariable(memory_book *M, string Variable)
return Result;
}
role *
GetRole(config *C, resolution_errors *E, config_pair *RoleID)
{
role *Result = 0;
for(int i = 0; i < C->Role.ItemCount; ++i)
{
role *Role = GetPlaceInBook(&C->Role, i);
if(!StringsDifferCaseInsensitive(Role->ID, RoleID->Value))
{
Result = Role;
break;
}
}
if(!Result && !GetError(E, S_WARNING, &RoleID->Position, IDENT_ROLE))
{
string Filepath = Wrap0(RoleID->Position.Filename);
ConfigError(&Filepath, RoleID->Position.LineNumber, S_WARNING, "Could not find role: ", &RoleID->Value);
PushError(E, S_WARNING, &RoleID->Position, IDENT_ROLE);
}
return Result;
}
role *
GetRoleByID(config *C, string ID)
{
role *Result = 0;
for(int i = 0; i < C->Role.ItemCount; ++i)
{
role *Role = GetPlaceInBook(&C->Role, i);
if(!StringsDifferCaseInsensitive(Role->ID, ID))
{
Result = Role;
break;
}
}
return Result;
}
person *
GetPerson(config *C, resolution_errors *E, config_pair *PersonID)
{
person *Result = 0;
for(int i = 0; i < C->Person.ItemCount; ++i)
{
person *Person = GetPlaceInBook(&C->Person, i);
if(!StringsDifferCaseInsensitive(Person->ID, PersonID->Value))
{
return Person;
Result = Person;
break;
}
}
if(!GetError(E, S_WARNING, &PersonID->Position, IDENT_PERSON))
if(!Result && !GetError(E, S_WARNING, &PersonID->Position, IDENT_PERSON))
{
string Filepath = Wrap0(PersonID->Position.Filename);
ConfigError(&Filepath, PersonID->Position.LineNumber, S_WARNING, "Could not find person: ", &PersonID->Value);
PushError(E, S_WARNING, &PersonID->Position, IDENT_PERSON);
}
return 0;
return Result;
}
string
@ -2712,6 +2886,8 @@ ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_
case IDENT_OWNER:
{
bool Processed = FALSE;
while(!Processed && Scope)
{
for(int i = 0; i < Scope->Pairs.ItemCount; ++i)
{
config_pair *Pair = GetPlaceInBook(&Scope->Pairs, i);
@ -2734,6 +2910,8 @@ ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_
}
}
}
Scope = Scope->Parent;
}
if(!Processed)
{
if(!GetError(E, S_WARNING, Position, Variable))
@ -2745,7 +2923,8 @@ ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_
} break;
case IDENT_PERSON:
{
if(Scope->ID.Key != Variable)
// NOTE(matt): Allow scopes within a person scope, e.g. support, to resolve $person
while(Scope && Scope->ID.Key != Variable)
{
Scope = Scope->Parent;
}
@ -3123,7 +3302,20 @@ PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope
{
This->Homepage = ResolveString(C, E, PersonTree, Pair, FALSE);
}
#if(HMMLIB_MAJOR_VERSION == 2)
else if(IDENT_QUOTE_USERNAME == Pair->Key)
{
This->QuoteUsername = ResolveString(C, E, PersonTree, Pair, FALSE);
}
#endif
}
#if(HMMLIB_MAJOR_VERSION == 2)
if(This->QuoteUsername.Length == 0)
{
This->QuoteUsername = This->ID;
}
#endif
This->Support = InitBook(MBT_SUPPORT, 2);
for(int i = 0; i < PersonTree->Trees.ItemCount; ++i)
@ -3133,6 +3325,35 @@ PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope
}
}
void
PushRoleOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *RoleTree)
{
role *This = MakeSpaceInBook(&C->Role);
This->ID = ResolveString(C, E, RoleTree, &RoleTree->ID, FALSE);
for(int i = 0; i < RoleTree->Pairs.ItemCount; ++i)
{
config_pair *Pair = GetPlaceInBook(&RoleTree->Pairs, i);
if(IDENT_NAME == Pair->Key)
{
This->Name = ResolveString(C, E, RoleTree, Pair, FALSE);
}
else if(IDENT_PLURAL == Pair->Key)
{
This->Plural = ResolveString(C, E, RoleTree, Pair, FALSE);
}
}
for(int i = 0; i < RoleTree->BoolPairs.ItemCount; ++i)
{
config_bool_pair *BoolPair = GetPlaceInBook(&RoleTree->BoolPairs, i);
if(IDENT_NON_SPEAKING == BoolPair->Key)
{
This->NonSpeaking = BoolPair->Value;
}
}
}
#if(HMMLIB_MAJOR_VERSION == 2)
#else
void
PushPersonOntoProject(config *C, resolution_errors *E, project *P, config_pair *Actor)
{
@ -3160,6 +3381,32 @@ PushPersonOntoProject(config *C, resolution_errors *E, project *P, config_pair *
}
}
}
#endif
void
PushCredit(config *C, resolution_errors *E, project *P, scope_tree *CreditTree)
{
CreditTree->ID.Value = ResolveString(C, E, CreditTree, &CreditTree->ID, FALSE);
if(CreditTree->ID.Value.Base)
{
person *Person = GetPerson(C, E, &CreditTree->ID);
if(Person)
{
for(int i = 0; i < CreditTree->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&CreditTree->Pairs, i);
role *Role = GetRole(C, E, This);
if(Role)
{
credit *Credit = MakeSpaceInBook(&P->Credit);
// TODO(matt): Sort the P->Credit book by Person->SortName
Credit->Role = Role;
Credit->Person = Person;
}
}
}
}
}
void PushProjectOntoProject(config *C, resolution_errors *E, config_verifiers *Verifiers, project *Parent, scope_tree *ProjectTree);
@ -3255,9 +3502,13 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc
{
P->Medium = InitBook(MBT_MEDIUM, 8);
P->Child = InitBook(MBT_PROJECT, 8);
#if(HMMLIB_MAJOR_VERSION == 2)
P->Credit = InitBook(MBT_CREDIT, 4);
#else
P->Indexer = InitBookOfPointers(MBT_PERSON_PTR, 4);
P->CoHost = InitBookOfPointers(MBT_PERSON_PTR, 4);
P->Guest = InitBookOfPointers(MBT_PERSON_PTR, 4);
#endif
config_string_associations *HMMLDirs = &V->HMMLDirs;
P->ID = ResolveString(C, E, ProjectTree, &ProjectTree->ID, FALSE);
@ -3271,12 +3522,40 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc
ResetPen(&C->ResolvedVariables);
P->Lineage = DeriveLineageOfProject(C, ProjectTree);
// NOTE(matt): Initial pass over as-yet unresolvable local variable(s)
//
for(int i = 0; i < ProjectTree->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&ProjectTree->Pairs, i);
switch(This->Key)
{
case IDENT_OWNER:
{
// TODO(matt): Do we need to fail completely if owner cannot be found?
P->Owner = GetPerson(C, E, This);
} break;
default: break;
}
}
//
////
for(int i = 0; i < ProjectTree->Trees.ItemCount; ++i)
{
scope_tree *This = GetPlaceInBook(&ProjectTree->Trees, i);
if(This->ID.Key == IDENT_MEDIUM)
switch(This->ID.Key)
{
case IDENT_MEDIUM:
{
PushMedium(C, E, V, P, This);
} break;
#if(HMMLIB_MAJOR_VERSION == 2)
case IDENT_CREDIT:
{
PushCredit(C, E, P, This);
} break;
#endif
default: break;
}
}
@ -3290,15 +3569,13 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc
{ P->DefaultMedium = GetMediumFromProject(P, This->Value); } break;
case IDENT_HMML_DIR:
{ SetUniqueHMMLDir(C, E, HMMLDirs, P, ProjectTree, This); } break;
#if(HMMLIB_MAJOR_VERSION == 2)
#else
case IDENT_INDEXER:
case IDENT_COHOST:
case IDENT_GUEST:
{ PushPersonOntoProject(C, E, P, This); } break;
case IDENT_OWNER:
{
// TODO(matt): Do we need to fail completely if owner cannot be found?
P->Owner = GetPerson(C, E, This);
} break;
#endif
case IDENT_PLAYER_TEMPLATE:
{ P->PlayerTemplatePath = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); } break;
case IDENT_THEME:
@ -3371,8 +3648,11 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc
{ P->SearchTemplatePath = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); } break;
case IDENT_TEMPLATES_DIR:
{ P->TemplatesDir = StripSlashes(ResolveString(C, E, ProjectTree, This, TRUE), P_ABS); } break;
#if(HMMLIB_MAJOR_VERSION == 2)
#else
case IDENT_STREAM_USERNAME:
{ P->StreamUsername = ResolveString(C, E, ProjectTree, This, FALSE); } break;
#endif
case IDENT_VOD_PLATFORM:
{ P->VODPlatform = ResolveString(C, E, ProjectTree, This, FALSE); } break;
@ -3841,7 +4121,6 @@ TypesetVariants(typography *T, uint8_t Generation, config_identifier_id Key, uin
void
PrintMedium(typography *T, medium *M, char *Delimiter, uint64_t Indentation, bool AppendNewline)
{
// TODO(matt): Print Me!
PrintStringC(CS_BLUE_BOLD, M->ID);
if(M->Hidden)
{
@ -3970,7 +4249,7 @@ GetRowsRequiredForPersonInfo(typography *T, person *P)
}
void
PrintPerson(person *P, config_identifier_id Role, typography *Typography)
PrintPerson(person *P, typography *Typography)
{
bool HaveInfo = P->Name.Length > 0 || P->Homepage.Length > 0 || P->Support.ItemCount > 0;
fprintf(stderr, "\n"
@ -4003,6 +4282,49 @@ PrintPerson(person *P, config_identifier_id Role, typography *Typography)
}
}
uint8_t
GetRowsRequiredForRoleInfo(role *R)
{
uint8_t RowsRequired = 0;
if(R->Name.Length > 0) { ++RowsRequired; }
if(R->Plural.Length > 0) { ++RowsRequired; }
int NonSpeakingLines = 1;
RowsRequired += NonSpeakingLines;
return RowsRequired;
}
void
PrintRole(role *R, typography *Typography)
{
bool HaveInfo = R->Name.Length > 0 || R->Plural.Length > 0;
fprintf(stderr, "\n"
"%s%s%s%s%s ", HaveInfo ? Typography->UpperLeftCorner : Typography->UpperLeft,
Typography->Horizontal, Typography->Horizontal, Typography->Horizontal, Typography->UpperRight);
PrintStringC(CS_GREEN_BOLD, R->ID);
fprintf(stderr, "\n");
uint8_t RowsRequired = GetRowsRequiredForRoleInfo(R);
int IndentationLevel = 0;
bool ShouldFillSyntax = FALSE;
if(R->Name.Length > 0)
{
fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair Name = { .Key = IDENT_NAME, .Value = R->Name }; PrintPair(&Name, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
if(R->Plural.Length > 0)
{
fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair Plural = { .Key = IDENT_PLURAL, .Value = R->Plural }; PrintPair(&Plural, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_bool_pair NonSpeaking = { .Key = IDENT_NON_SPEAKING, .Value = R->NonSpeaking }; PrintBoolPair(&NonSpeaking, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
void
PrintLineage(string Lineage, bool AppendNewline)
{
@ -4026,7 +4348,8 @@ GetColumnsRequiredForMedium(medium *M, typography *T)
}
void
PrintTitle(char *Text, int AvailableColumns)
PrintTitle(char *Text, typography *T, uint8_t Generation, int AvailableColumns,
int64_t SectionItemCount /* NOTE(matt): Use any non-0 when the section is known to have content */)
{
char *LeftmostChar = "";
char *InnerChar = "";
@ -4049,6 +4372,11 @@ PrintTitle(char *Text, int AvailableColumns)
fprintf(stderr, "%s", InnerChar);
}
fprintf(stderr, "%s", RightmostChar);
if(SectionItemCount == 0)
{
//fprintf(stderr, "\n");
CarriageReturn(T, Generation);
}
}
void
@ -4057,7 +4385,7 @@ PrintMedia(project *P, typography *T, uint8_t Generation, int AvailableColumns)
int IndentationLevel = 0;
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
PrintTitle("Media", AvailableColumns);
PrintTitle("Media", T, Generation, AvailableColumns, P->Medium.ItemCount);
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
//AvailableColumns -= Generation + 1;
@ -4106,6 +4434,63 @@ PrintMedia(project *P, typography *T, uint8_t Generation, int AvailableColumns)
}
}
void
PrintCredits(config *C, project *P, typography *T, uint8_t Generation, int IndentationLevel, int AvailableColumns)
{
for(int i = 0; i < C->Role.ItemCount; ++i)
{
role *Role = GetPlaceInBook(&C->Role, i);
int CreditCount = 0;
for(int j = 0; j < P->Credit.ItemCount; ++j)
{
credit *Credit = GetPlaceInBook(&P->Credit, j);
if(Role == Credit->Role)
{
++CreditCount;
}
}
if(CreditCount > 0)
{
CarriageReturn(T, Generation);
int Alignment = 0;
Alignment += fprintf(stderr, "%s", T->Margin);
if(CreditCount == 1)
{
Alignment += PrintStringC(CS_YELLOW_BOLD, Role->Name.Length ? Role->Name : Role->ID);
}
else
{
if(Role->Plural.Length > 0)
{
Alignment += PrintStringC(CS_YELLOW_BOLD, Role->Plural);
}
else
{
Alignment += PrintStringC(CS_YELLOW_BOLD, Role->Name.Length ? Role->Name : Role->ID);
Alignment += PrintStringC(CS_YELLOW_BOLD, Wrap0("s"));
}
}
Alignment += fprintf(stderr, "%s", T->Delimiter);
bool Printed = FALSE;
for(int j = 0; j < P->Credit.ItemCount; ++j)
{
credit *Credit = GetPlaceInBook(&P->Credit, j);
if(Role == Credit->Role)
{
if(Printed)
{
CarriageReturn(T, Generation);
AlignText(Alignment);
}
PrintStringC(CS_GREEN_BOLD, Credit->Person->Name.Length ? Credit->Person->Name : Credit->Person->ID);
Printed = TRUE;
}
}
}
}
}
void
TypesetPair(typography *T, uint8_t Generation, config_identifier_id Key, string Value, int AvailableColumns)
{
@ -4177,7 +4562,7 @@ TypesetNumberingScheme(typography *T, uint8_t Generation, numbering_scheme N)
}
void
PrintProject(project *P, typography *T, int Ancestors, int IndentationLevel, int TerminalColumns)
PrintProject(config *C, project *P, typography *T, int Ancestors, int IndentationLevel, int TerminalColumns)
{
int Generation = Ancestors + 1;
CarriageReturn(T, Ancestors);
@ -4196,7 +4581,7 @@ PrintProject(project *P, typography *T, int Ancestors, int IndentationLevel, int
CarriageReturn(T, Generation);
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
PrintTitle("Settings", AvailableColumns);
PrintTitle("Settings", T, Generation, AvailableColumns, -1);
TypesetPair(T, Generation, IDENT_TITLE, P->Title, AvailableColumns);
TypesetPair(T, Generation, IDENT_HTML_TITLE, P->HTMLTitle, AvailableColumns);
@ -4213,7 +4598,10 @@ PrintProject(project *P, typography *T, int Ancestors, int IndentationLevel, int
TypesetPair(T, Generation, IDENT_BASE_URL, P->BaseURL, AvailableColumns);
TypesetPair(T, Generation, IDENT_SEARCH_LOCATION, P->SearchLocation, AvailableColumns);
TypesetPair(T, Generation, IDENT_PLAYER_LOCATION, P->PlayerLocation, AvailableColumns);
#if(HMMLIB_MAJOR_VERSION == 2)
#else
TypesetPair(T, Generation, IDENT_STREAM_USERNAME, P->StreamUsername, AvailableColumns);
#endif
TypesetPair(T, Generation, IDENT_VOD_PLATFORM, P->VODPlatform, AvailableColumns);
TypesetPair(T, Generation, IDENT_THEME, P->Theme, AvailableColumns);
@ -4233,11 +4621,10 @@ PrintProject(project *P, typography *T, int Ancestors, int IndentationLevel, int
TypesetBool(T, Generation, IDENT_IGNORE_PRIVACY, P->IgnorePrivacy);
TypesetBool(T, Generation, IDENT_SINGLE_BROWSER_TAB, P->SingleBrowserTab);
if(P->Owner)
{
TypesetPair(T, Generation, IDENT_OWNER, P->Owner->ID, AvailableColumns);
}
TypesetPair(T, Generation, IDENT_OWNER, P->Owner ? P->Owner->ID : Wrap0(""), AvailableColumns);
#if(HMMLIB_MAJOR_VERSION == 2)
#else
for(int i = 0; i < P->Indexer.ItemCount; ++i)
{
person **Indexer = GetPlaceInBook(&P->Indexer, i);
@ -4253,17 +4640,32 @@ PrintProject(project *P, typography *T, int Ancestors, int IndentationLevel, int
person **Guest = GetPlaceInBook(&P->Guest, i);
TypesetPair(T, Generation, IDENT_GUEST, (*Guest)->ID, AvailableColumns);
}
#endif
CarriageReturn(T, Generation);
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
PrintTitle("Credits", T, Generation, AvailableColumns, P->Credit.ItemCount);
if(P->Credit.ItemCount > 0)
{
PrintCredits(C, P, T, Generation, IndentationLevel, AvailableColumns);
}
else
{
fprintf(stderr, "%s", T->Margin);
PrintC(CS_YELLOW, "[none]");
}
if(P->Child.ItemCount)
{
CarriageReturn(T, Generation);
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
PrintTitle("Children", AvailableColumns);
PrintTitle("Children", T, Generation, AvailableColumns, -1);
++Ancestors;
for(int i = 0; i < P->Child.ItemCount; ++i)
{
PrintProject(GetPlaceInBook(&P->Child, i), T, Ancestors, IndentationLevel, TerminalColumns);
PrintProject(C, GetPlaceInBook(&P->Child, i), T, Ancestors, IndentationLevel, TerminalColumns);
}
--Ancestors;
}
@ -4296,7 +4698,7 @@ PrintConfig(config *C, bool ShouldClearTerminal)
// separate
fprintf(stderr, "\n");
PrintTitle("Global Settings", TermCols);
PrintTitle("Global Settings", &Typography, 0, TermCols, -1);
int IndentationLevel = 0;
int AvailableColumns = TermCols - StringLength(Typography.Margin);
@ -4330,17 +4732,24 @@ PrintConfig(config *C, bool ShouldClearTerminal)
PrintLogLevel(ConfigIdentifiers[IDENT_LOG_LEVEL].String, C->LogLevel, Typography.Delimiter, IndentationLevel, TRUE);
fprintf(stderr, "\n");
PrintTitle("People", TermCols);
PrintTitle("People", &Typography, 0, TermCols, C->Person.ItemCount);
for(int i = 0; i < C->Person.ItemCount; ++i)
{
PrintPerson(GetPlaceInBook(&C->Person, i), IDENT_NULL, &Typography);
PrintPerson(GetPlaceInBook(&C->Person, i), &Typography);
}
fprintf(stderr, "\n");
PrintTitle("Projects", TermCols);
PrintTitle("Roles", &Typography, 0, TermCols, C->Role.ItemCount);
for(int i = 0; i < C->Role.ItemCount; ++i)
{
PrintRole(GetPlaceInBook(&C->Role, i), &Typography);
}
fprintf(stderr, "\n");
PrintTitle("Projects", &Typography, 0, TermCols, -1);
for(int i = 0; i < C->Project.ItemCount; ++i)
{
PrintProject(GetPlaceInBook(&C->Project, i), &Typography, 0, IndentationLevel, TermCols);
PrintProject(C, GetPlaceInBook(&C->Project, i), &Typography, 0, IndentationLevel, TermCols);
}
}
}
@ -4349,10 +4758,13 @@ void
FreeProject(project *P)
{
FreeBook(&P->Medium);
#if(HMMLIB_MAJOR_VERSION == 2)
FreeBook(&P->Credit);
#else
FreeBook(&P->Indexer);
FreeBook(&P->Guest);
FreeBook(&P->CoHost);
#endif
for(int i = 0; i < P->Child.ItemCount; ++i)
{
FreeProject(GetPlaceInBook(&P->Child, i));
@ -4373,6 +4785,7 @@ FreeProject(project *P)
FreeBook(&Person->Support);\
}\
FreeBook(&C->Person);\
FreeBook(&C->Role);\
for(int i = 0; i < C->Project.ItemCount; ++i)\
{\
FreeProject(GetPlaceInBook(&C->Project, i));\
@ -4404,6 +4817,93 @@ InitVerifiers(void)
return Result;
}
bool
IsPositioned(config_int_pair *Position)
{
return Position && Position->Value != 0;
}
void
PositionRolesInConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *S)
{
memory_book RolesStaging = InitBookOfPointers(MBT_SCOPE_TREE_PTR, 4);
for(int i = 0; i < S->Trees.ItemCount; ++i)
{
scope_tree *Tree = GetPlaceInBook(&S->Trees, i);
if(Tree->ID.Key == IDENT_ROLE)
{
scope_tree **This = MakeSpaceInBook(&RolesStaging);
*This = Tree;
}
}
int SlotCount = RolesStaging.ItemCount;
scope_tree *RoleSlot[SlotCount];
Clear(RoleSlot, sizeof(scope_tree *) * SlotCount);
for(int i = 0; i < SlotCount; ++i)
{
scope_tree **Role = GetPlaceInBook(&RolesStaging, i);
config_int_pair *Position = GetIntPair(*Role, IDENT_POSITION);
if(IsPositioned(Position))
{
int TargetPos;
if(Position->Value < 0)
{
TargetPos = SlotCount + Position->Value;
Clamp(0, TargetPos, SlotCount - 1);
while(RoleSlot[TargetPos] && TargetPos > 0)
{
--TargetPos;
}
while(RoleSlot[TargetPos] && TargetPos < SlotCount - 1)
{
++TargetPos;
}
}
else if(Position->Value > 0)
{
TargetPos = Position->Value - 1;
Clamp(0, TargetPos, SlotCount - 1);
while(RoleSlot[TargetPos] && TargetPos < SlotCount - 1)
{
++TargetPos;
}
while(RoleSlot[TargetPos] && TargetPos > 0)
{
--TargetPos;
}
}
RoleSlot[TargetPos] = *Role;
}
}
for(int i = 0; i < SlotCount; ++i)
{
scope_tree **Role = GetPlaceInBook(&RolesStaging, i);
config_int_pair *Position = GetIntPair(*Role, IDENT_POSITION);
if(!IsPositioned(Position))
{
for(int j = 0; j < SlotCount; ++j)
{
if(!RoleSlot[j])
{
RoleSlot[j] = *Role;
break;
}
}
}
}
for(int i = 0; i < SlotCount; ++i)
{
PushRoleOntoConfig(C, E, V, RoleSlot[i]);
}
FreeBook(&RolesStaging);
}
config *
ResolveVariables(scope_tree *S)
{
@ -4411,6 +4911,7 @@ ResolveVariables(scope_tree *S)
config *Result = calloc(1, sizeof(config));
Result->ResolvedVariables = InitBookOfStrings(Kilobytes(1));
Result->Person = InitBook(MBT_PERSON, 8);
Result->Role = InitBook(MBT_ROLE, 4);
Result->Project = InitBook(MBT_PROJECT, 8);
resolution_errors Errors = {};
@ -4553,6 +5054,8 @@ ResolveVariables(scope_tree *S)
}
}
PositionRolesInConfig(Result, &Errors, &Verifiers, S);
for(int i = 0; i < S->Trees.ItemCount; ++i)
{
scope_tree *Tree = GetPlaceInBook(&S->Trees, i);