Annotation-System/cinera/cinera_config.c

4673 lines
162 KiB
C

#if 0
ctime -begin ${0%.*}.ctm
gcc -g -Wall -fsanitize=address -std=c99 $0 -o ${0%.*}
#clang -g -fsanitize=address -std=c99 $0 -o ${0%.*}
ctime -end ${0%.*}.ctm
exit
#endif
// config
//
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#define ArgCountPointer(...) (sizeof((void *[]){__VA_ARGS__})/sizeof(void *))
#define DigitsInInt(I) DigitsInInt_(I, sizeof(*I))
uint8_t
DigitsInInt_(void *Int, uint8_t WidthInBytes)
{
uint8_t Result = 0;
switch(WidthInBytes)
{
case 1:
{
int8_t This = *(int8_t *)Int;
if(This <= 0) { This = ~This; ++Result; if(This == 0) { ++Result; return Result; } }
for(; This > 0; This /= 10) { ++Result; }
} break;
case 2:
{
int16_t This = *(int16_t *)Int;
if(This <= 0) { This = ~This; ++Result; if(This == 0) { ++Result; return Result; } }
for(; This > 0; This /= 10) { ++Result; }
} break;
case 4:
{
int32_t This = *(int32_t *)Int;
if(This <= 0) { This = ~This; ++Result; if(This == 0) { ++Result; return Result; } }
for(; This > 0; This /= 10) { ++Result; }
} break;
case 8:
{
int64_t This = *(int64_t *)Int;
if(This <= 0) { This = ~This; ++Result; if(This == 0) { ++Result; return Result; } }
for(; This > 0; This /= 10) { ++Result; }
} break;
default:
{
Assert(0);
}
}
return Result;
}
#define DigitsInUint(I) DigitsInUint_(I, sizeof(*I))
uint8_t
DigitsInUint_(void *Uint, uint8_t WidthInBytes)
{
uint32_t Result = 0;
switch(WidthInBytes)
{
case 1:
{
uint8_t This = *(uint8_t *)Uint;
if(This == 0) { ++Result; return Result; }
for(; This > 0; This /= 10) { ++Result; }
} break;
case 2:
{
uint16_t This = *(uint16_t *)Uint;
if(This == 0) { ++Result; return Result; }
for(; This > 0; This /= 10) { ++Result; }
} break;
case 4:
{
uint32_t This = *(uint32_t *)Uint;
if(This == 0) { ++Result; return Result; }
for(; This > 0; This /= 10) { ++Result; }
} break;
case 8:
{
uint64_t This = *(uint64_t *)Uint;
if(This == 0) { ++Result; return Result; }
for(; This > 0; This /= 10) { ++Result; }
} break;
default:
{
Assert(0);
}
}
return Result;
}
#define MakeString(Format, ...) MakeString_(__FILE__, __LINE__, Format, ArgCountPointer(__VA_ARGS__), __VA_ARGS__)
string
MakeString_(char *Filename, int LineNumber, char *Format, int ArgCount, ...)
{
// NOTE(matt): Be sure to free built strings when you're finished with them
//
// Usage:
//
// Similar to printf and friends, MakeString() takes a format string as its first argument.
// While printf has many format specifiers - e.g. %s, %d, %c - MakeString() has two: s, l
// s: Null-terminated string, i.e. char *
// l: Length-string, i.e. string *
// We allocate memory, null-terminate and return the char *
//
// Examples:
// 1. char *BuiltString = MakeString("ss", "First", "Second");
// 2. char *Input = "First";
// char *BuiltString = MakeString("ss", Input, "Second");
// 3. string Input = { .Base = "Second", .Length = StringLength("Second") };
// char *BuiltString = MakeString("sl", "First", &Input);
int FormatLength = StringLength(Format);
if(FormatLength != ArgCount)
{
Colourise(CS_FAILURE); fprintf(stderr, "%s:%d MakeString()", Filename, LineNumber); Colourise(CS_END);
fprintf(stderr,
"\n"
" MakeString() has been passed a format string containing ");
Colourise(CS_BLUE_BOLD); fprintf(stderr, "%d specifier%s", FormatLength, FormatLength == 1 ? "" : "s"); Colourise(CS_END);
fprintf(stderr,
",\n"
" and ");
Colourise(CS_BLUE_BOLD); fprintf(stderr, "%d argument%s", ArgCount, ArgCount == 1 ? "" : "s"); Colourise(CS_END);
fprintf(stderr, ", but these numbers should be equal.\n");
__asm__("int3");
}
// TODO(matt): Type-checking, if possible?
va_list Args;
va_start(Args, ArgCount);
string Result = {};
for(int i = 0; i < FormatLength; ++i)
{
switch(Format[i])
{
case 's':
{
char *Next = va_arg(Args, char *);
int NextLength = StringLength(Next);
Result.Base = realloc(Result.Base, Result.Length + NextLength);
char *Ptr = Result.Base + Result.Length;
while(*Next) { *Ptr++ = *Next++; }
Result.Length += NextLength;
} break;
case 'l':
{
string *Next = va_arg(Args, string *);
Result.Base = realloc(Result.Base, Result.Length + Next->Length);
char *Ptr = Result.Base + Result.Length;
int j;
for(j = 0; j < Next->Length && Next->Base[j]; ++j)
{
*Ptr++ = Next->Base[j];
}
Result.Length += j;
} break;
default:
{
Colourise(CS_FAILURE); fprintf(stderr, "%s:%d", Filename, LineNumber); Colourise(CS_END);
fprintf(stderr, "\n"
" MakeString() has been passed an unsupported format specifier: %c\n", Format[i]);
__asm__("int3");
} break;
}
}
va_end(Args);
return Result;
}
#define MakeString0(Format, ...) MakeString0_(__FILE__, __LINE__, Format, ArgCountPointer(__VA_ARGS__), __VA_ARGS__)
char *
MakeString0_(char *Filename, int LineNumber, char *Format, int ArgCount, ...)
{
// NOTE(matt): Be sure to free built strings when you're finished with them
//
// Usage:
//
// Similar to printf and friends, MakeString() takes a format string as its first argument.
// While printf has many format specifiers - e.g. %s, %d, %c - MakeString() has two: s, l
// s: Null-terminated string, i.e. char *
// l: Length-string, i.e. string *
// We allocate memory, null-terminate and return the char *
//
// Examples:
// 1. char *BuiltString = MakeString("ss", "First", "Second");
// 2. char *Input = "First";
// char *BuiltString = MakeString("ss", Input, "Second");
// 3. string Input = { .Base = "Second", .Length = StringLength("Second") };
// char *BuiltString = MakeString("sl", "First", &Input);
int FormatLength = StringLength(Format);
if(FormatLength != ArgCount)
{
Colourise(CS_FAILURE); fprintf(stderr, "%s:%d MakeString0()", Filename, LineNumber); Colourise(CS_END);
fprintf(stderr,
"\n"
" MakeString0() has been passed a format string containing ");
Colourise(CS_BLUE_BOLD); fprintf(stderr, "%d specifier%s", FormatLength, FormatLength == 1 ? "" : "s"); Colourise(CS_END);
fprintf(stderr,
",\n"
" and ");
Colourise(CS_BLUE_BOLD); fprintf(stderr, "%d argument%s", ArgCount, ArgCount == 1 ? "" : "s"); Colourise(CS_END);
fprintf(stderr, ", but these numbers should be equal.\n");
__asm__("int3");
}
// TODO(matt): Type-checking, if possible?
va_list Args;
va_start(Args, ArgCount);
char *Result = 0;
int RequiredSpace = 0;
for(int i = 0; i < FormatLength; ++i)
{
switch(Format[i])
{
case 's':
{
char *Next = va_arg(Args, char *);
int NextLength = StringLength(Next);
Result = realloc(Result, RequiredSpace + NextLength);
char *Ptr = Result + RequiredSpace;
while(*Next) { *Ptr++ = *Next++; }
RequiredSpace += NextLength;
} break;
case 'l':
{
string *Next = va_arg(Args, string *);
Result = realloc(Result, RequiredSpace + Next->Length);
char *Ptr = Result + RequiredSpace;
int j;
for(j = 0; j < Next->Length && Next->Base[j]; ++j)
{
*Ptr++ = Next->Base[j];
}
RequiredSpace += j;
} break;
case 'd':
{
int32_t Next = *va_arg(Args, int32_t *);
int NextLength = DigitsInInt(&Next);
Result = realloc(Result, RequiredSpace + NextLength);
char *Ptr = Result + RequiredSpace;
sprintf(Ptr, "%d", Next);
RequiredSpace += NextLength;
} break;
case 'u':
{
uint32_t Next = *va_arg(Args, uint32_t *);
int NextLength = DigitsInUint(&Next);
Result = realloc(Result, RequiredSpace + NextLength);
char *Ptr = Result + RequiredSpace;
sprintf(Ptr, "%u", Next);
RequiredSpace += NextLength;
} break;
default:
{
Colourise(CS_FAILURE); fprintf(stderr, "%s:%d", Filename, LineNumber); Colourise(CS_END);
fprintf(stderr, "\n"
" MakeString0() has been passed an unsupported format specifier: %c\n", Format[i]);
__asm__("int3");
} break;
}
}
va_end(Args);
++RequiredSpace;
Result = realloc(Result, RequiredSpace);
Result[RequiredSpace - 1] = '\0';
return Result;
}
void
PushToken(tokens *Set, token *Token)
{
// NOTE(matt): We don't bother to push comments or newlines on to the token list
//PrintFunctionName("PushToken()");
if(!(Token->Type == TOKEN_NULL || Token->Type == TOKEN_COMMENT || Token->Type == TOKEN_NEWLINE))
{
token *This = GetPlaceInBook(&Set->Token, Set->Token.ItemCount);
This->Type = Token->Type;
This->LineNumber = Set->CurrentLine;
if(Token->Type == TOKEN_NUMBER)
{
This->int64_t = StringToInt(Token->Content);
}
else
{
This->Content = Token->Content;
}
++Set->Token.ItemCount;
}
}
void
PrintToken(token *T, uint64_t Index)
{
fprintf(stderr, "[%lu, %s] ", Index, TokenTypeStrings[T->Type]);
if(T->Type == TOKEN_NUMBER)
{
Colourise(CS_BLUE_BOLD);
fprintf(stderr, "%li", T->int64_t);
Colourise(CS_END);
}
else if(T->Type == TOKEN_COMMENT)
{
PrintStringC(CS_BLACK_BOLD, T->Content);
}
else
{
PrintString(T->Content);
}
fprintf(stderr, "\n");
}
void
PrintTokens(tokens *T)
{
//PrintFunctionName("PrintTokens()");
for(int i = 0; i < T->Token.ItemCount; ++i)
{
token *This = GetPlaceInBook(&T->Token, i);
PrintToken(This, i);
}
}
tokens *
PushTokens(memory_book *TokensList)
{
tokens *This = GetPlaceInBook(TokensList, TokensList->ItemCount);
InitBook(&This->Token, sizeof(token), 16, MBT_TOKEN);
This->CurrentLine = 1;
++TokensList->ItemCount;
return This;
}
void
FreeTokensList(memory_book *TokensList)
{
for(int i = 0; i < TokensList->ItemCount; ++i)
{
tokens *This = GetPlaceInBook(TokensList, i);
This->CurrentIndex = 0;
This->CurrentLine = 0;
FreeBook(&This->Token);
FreeFile(&This->File);
}
TokensList->ItemCount = 0;
}
void
SkipWhitespace(tokens *T, buffer *B)
{
while(B->Ptr - B->Location < B->Size &&
(*B->Ptr == ' ' ||
//*B->Ptr == '\n' ||
*B->Ptr == '\t'))
{
++B->Ptr;
}
}
void
Advance(buffer *B, uint64_t Distance)
{
if(B->Ptr + Distance - B->Location <= B->Size)
{
B->Ptr += Distance;
}
}
string
GetUTF8Character(char *Ptr, uint64_t BytesRemaining)
{
string Result = {};
if((uint8_t)(Ptr[0] & 0b11111000) == 0b11110000)
{
++Result.Length;
if(BytesRemaining >= 3)
{
if((uint8_t)(Ptr[1] & 0b11000000) == 0b10000000 &&
(uint8_t)(Ptr[2] & 0b11000000) == 0b10000000 &&
(uint8_t)(Ptr[3] & 0b11000000) == 0b10000000)
{
Result.Length = 4;
Result.Base = Ptr;
return Result;
}
}
}
else if((uint8_t)(Ptr[0] & 0b11110000) == 0b11100000)
{
++Result.Length;
if(BytesRemaining >= 2)
{
if((uint8_t)(Ptr[1] & 0b11000000) == 0b10000000 &&
(uint8_t)(Ptr[2] & 0b11000000) == 0b10000000)
{
Result.Length = 3;
Result.Base = Ptr;
return Result;
}
}
}
else if((uint8_t)(Ptr[0] & 0b11100000) == 0b11000000)
{
++Result.Length;
if(BytesRemaining >= 1)
{
if((uint8_t)(Ptr[1] & 0b11000000) == 0b10000000)
{
Result.Length = 2;
Result.Base = Ptr;
return Result;
}
}
}
else if((uint8_t)(Ptr[0] & 0b10000000) == 0b00000000)
{
++Result.Length;
Result.Base = Ptr;
return Result;
}
for(; BytesRemaining >= Result.Length; ++Result.Length)
{
if(((uint8_t)(Ptr[Result.Length] & 0b11111000) == 0b11110000) ||
((uint8_t)(Ptr[Result.Length] & 0b11110000) == 0b11100000) ||
((uint8_t)(Ptr[Result.Length] & 0b11100000) == 0b11000000) ||
((uint8_t)(Ptr[Result.Length] & 0b10000000) == 0b00000000))
{
break;
}
}
return Result;
}
typedef struct
{
string ID;
string Icon;
string IconNormal;
string IconFocused;
icon_type IconType;
uint64_t IconVariants;
asset *IconAsset;
string URL;
} support;
typedef struct
{
string ID;
string Icon;
string IconNormal;
string IconDisabled;
string IconFocused;
icon_type IconType;
uint64_t IconVariants;
asset *IconAsset;
string Name;
bool Hidden;
} medium;
typedef struct
{
string ID;
string Name;
string Homepage;
_memory_book(support) Support;
} person;
typedef struct project
{
string ID;
string Lineage;
string Title;
string HTMLTitle;
string Unit;
string HMMLDir;
string TemplatesDir;
string SearchTemplatePath;
string PlayerTemplatePath;
string BaseDir;
string BaseURL;
string SearchLocation;
string PlayerLocation;
string Theme;
string Art;
uint64_t ArtVariants;
asset *ArtAsset;
string Icon;
string IconNormal;
string IconDisabled;
string IconFocused;
icon_type IconType;
uint64_t IconVariants;
asset *IconAsset;
string StreamUsername;
string VODPlatform;
bool DenyBespokeTemplates;
bool SingleBrowserTab;
bool IgnorePrivacy;
person *Owner;
_memory_book(person) Indexer;
_memory_book(person) Guest;
_memory_book(person) CoHost;
_memory_book(medium) Medium;
medium *DefaultMedium;
_memory_book(project) Child;
struct project *Parent;
genre Genre;
numbering_scheme NumberingScheme;
db_project_index Index;
template PlayerTemplate;
template SearchTemplate;
} project;
typedef struct
{
string DatabaseLocation;
string CacheDir;
string GlobalTemplatesDir;
string GlobalSearchTemplatePath;
string GlobalSearchDir;
string GlobalSearchURL;
string GlobalTheme;
template SearchTemplate;
string AssetsRootDir;
string AssetsRootURL;
string CSSDir;
string ImagesDir;
string JSDir;
string QueryString;
bool RespectingPrivacy;
time_t PrivacyCheckInterval;
uint8_t LogLevel;
_memory_book(person) Person;
_memory_book(project) Project;
memory_book ResolvedVariables;
} config;
char *ExpandPath(string Path, string *RelativeToFile); // NOTE(matt): Forward declared. Consider reorganising the code?
void PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *Project, asset *Asset); // NOTE(matt): Forward declared. Consider reorganising the code?
tokens *
Tokenise(memory_book *TokensList, string Path)
{
tokens *Result = 0;
char *Path0 = MakeString0("l", &Path);
FILE *Handle = 0;
PushWatchHandle(Path, EXT_NULL, WT_CONFIG, 0, 0);
if((Handle = fopen(Path0, "r")))
{
fclose(Handle);
Result = PushTokens(TokensList);
Result->File = InitFile(0, &Path, EXT_NULL);
ReadFileIntoBuffer(&Result->File);
buffer *B = &Result->File.Buffer;
SkipWhitespace(Result, B);
while(B->Ptr - B->Location < B->Size)
{
token T = {};
uint64_t Advancement = 0;
if(!StringsDifferS(TokenStrings[TOKEN_COMMENT_SINGLE], B))
{
T.Type = TOKEN_COMMENT;
B->Ptr += StringLength(TokenStrings[TOKEN_COMMENT_SINGLE]);
SkipWhitespace(Result, B);
T.Content.Base = B->Ptr;
while(B->Ptr && *B->Ptr != '\n')
{
++T.Content.Length;
++B->Ptr;
}
if(*B->Ptr == '\n')
{
++Result->CurrentLine;
}
Advancement = 1;
}
else if(!StringsDifferS(TokenStrings[TOKEN_COMMENT_MULTI_OPEN], B))
{
uint64_t CommentDepth = 1;
T.Type = TOKEN_COMMENT;
B->Ptr += StringLength(TokenStrings[TOKEN_COMMENT_MULTI_OPEN]);
SkipWhitespace(Result, B);
T.Content.Base = B->Ptr;
while(B->Ptr - B->Location < B->Size && CommentDepth)
{
if(!StringsDifferS(TokenStrings[TOKEN_COMMENT_MULTI_CLOSE], B))
{
--CommentDepth;
B->Ptr += StringLength(TokenStrings[TOKEN_COMMENT_MULTI_CLOSE]);
}
else if(B->Ptr - B->Location < B->Size && *B->Ptr == '\n')
{
++Result->CurrentLine;
++B->Ptr;
}
else if(!StringsDifferS(TokenStrings[TOKEN_COMMENT_MULTI_OPEN], B))
{
++CommentDepth;
B->Ptr += StringLength(TokenStrings[TOKEN_COMMENT_MULTI_OPEN]);
}
else
{
++B->Ptr;
}
}
T.Content.Length = B->Ptr - T.Content.Base;
Advancement = 0;//StringLength(TokenStrings[TOKEN_COMMENT_MULTI_CLOSE]);
}
else if(!StringsDifferS(TokenStrings[TOKEN_COMMENT_MULTI_CLOSE], B))
{
Advancement = 2;
T.Type = TOKEN_NULL;
string Char = { .Base = B->Ptr, .Length = 2 };
ConfigError(Result->File.Path, Result->CurrentLine, S_WARNING, "Mismatched closing multiline comment marker: ", &Char);
}
else if(!StringsDifferS(TokenStrings[TOKEN_DOUBLEQUOTE], B))
{
T.Type = TOKEN_STRING;
++B->Ptr;
T.Content.Base = B->Ptr;
while(B->Ptr - B->Location < B->Size && *B->Ptr != '"')
{
if(*B->Ptr == '\\')
{
++T.Content.Length;
++B->Ptr;
}
++T.Content.Length;
++B->Ptr;
}
Advancement = 1;
}
else if(!StringsDifferS(TokenStrings[TOKEN_ASSIGN], B))
{
T.Type = TOKEN_ASSIGN;
T.Content.Base = B->Ptr;
++T.Content.Length;
Advancement = 1;
}
else if(!StringsDifferS(TokenStrings[TOKEN_OPEN_BRACE], B))
{
T.Type = TOKEN_OPEN_BRACE;
T.Content.Base = B->Ptr;
++T.Content.Length;
Advancement = 1;
}
else if(!StringsDifferS(TokenStrings[TOKEN_CLOSE_BRACE], B))
{
T.Type = TOKEN_CLOSE_BRACE;
T.Content.Base = B->Ptr;
++T.Content.Length;
Advancement = 1;
}
else if(!StringsDifferS(TokenStrings[TOKEN_SEMICOLON], B))
{
T.Type = TOKEN_SEMICOLON;
T.Content.Base = B->Ptr;
++T.Content.Length;
Advancement = 1;
}
else if(IsValidIdentifierCharacter(*B->Ptr))
{
T.Type = TOKEN_IDENTIFIER;
T.Content.Base = B->Ptr;
while(IsValidIdentifierCharacter(*B->Ptr))
{
++T.Content.Length;
++B->Ptr;
}
Advancement = 0;
}
else if(IsNumber(*B->Ptr))
{
T.Type = TOKEN_NUMBER;
T.Content.Base = B->Ptr;
while(IsNumber(*B->Ptr))
{
++T.Content.Length;
++B->Ptr;
}
Advancement = 0;
}
else if(*B->Ptr == '\n')
{
T.Type = TOKEN_NEWLINE;
T.Content.Base = B->Ptr;
T.Content.Length = 1;
++Result->CurrentLine;
Advancement = 1;
}
else
{
T.Type = TOKEN_NULL;
string Char = GetUTF8Character(B->Ptr, B->Size - (B->Ptr - B->Location));
Advancement = Char.Length;
if(Char.Base)
{
ConfigError(Result->File.Path, Result->CurrentLine, S_WARNING, "Unhandled character (ignored): ", &Char);
}
else
{
ConfigErrorInt(Result->File.Path, Result->CurrentLine, S_WARNING, "Malformed UTF-8 bytes encountered (skipped): ", Char.Length);
}
}
PushToken(Result, &T);
Advance(B, Advancement);
SkipWhitespace(Result, B);
}
// TODO(matt): PushConfigWatchHandle()
}
else
{
ConfigError(0, 0, S_WARNING, "Unable to open config file: ", &Path);
}
Free(Path0);
return Result;
}
bool
TokenEquals(tokens *T, char *String)
{
int i = 0;
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
for(; i < This->Content.Length && *String && This->Content.Base[i] == String[i]; ++i)
{
}
if(i == This->Content.Length && !String[i])
{
return TRUE;
}
return FALSE;
}
bool
TokenIs(tokens *T, token_type Type)
{
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
return(This->Type == Type);
}
bool
ExpectToken(tokens *T, token_type Type, token *Dest)
{
bool Result = FALSE;
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
if(This->Type == Type)
{
Result = TRUE;
if(Dest)
{
*Dest = *This;
}
}
else
{
Dest = 0;
ConfigErrorExpectation(T, Type, 0);
}
++T->CurrentIndex;
return Result;
}
char *FieldTypeNames[] =
{
"bare",
"boolean",
"number",
"scope",
"string",
};
typedef enum
{
FT_BARE,
FT_BOOLEAN,
FT_NUMBER,
FT_SCOPE,
FT_STRING,
} config_field_type;
typedef struct
{
config_identifier_id ID;
config_field_type Type;
bool Singleton;
bool IsSetLocally;
} config_type_field;
typedef struct
{
config_identifier_id ID;
bool Permeable;
uint64_t FieldCount;
config_type_field *Field;
} config_type_spec;
typedef struct
{
uint64_t Count;
config_type_spec *Spec;
} config_type_specs;
config_type_spec *
PushTypeSpec(config_type_specs *S, config_identifier_id ID, bool IsPermeable)
{
//PrintFunctionName("PushTypeSpec()");
S->Spec = Fit(S->Spec, sizeof(*S->Spec), S->Count, 16, FALSE);
config_type_spec *Result = S->Spec + S->Count;
S->Spec[S->Count].ID = ID;
S->Spec[S->Count].FieldCount = 0;
S->Spec[S->Count].Field = 0;
S->Spec[S->Count].Permeable = IsPermeable;
++S->Count;
return Result;
}
void
PushTypeSpecField(config_type_spec *Spec, config_field_type Type, config_identifier_id ID, bool Singleton)
{
//PrintFunctionName("PushTypeSpecField()");
Spec->Field = Fit(Spec->Field, sizeof(*Spec->Field), Spec->FieldCount, 4, FALSE);
Spec->Field[Spec->FieldCount].Type = Type;
Spec->Field[Spec->FieldCount].ID = ID;
Spec->Field[Spec->FieldCount].Singleton = Singleton;
Spec->Field[Spec->FieldCount].IsSetLocally = FALSE;
++Spec->FieldCount;
}
config_type_specs
InitTypeSpecs(void)
{
//PrintFunctionName("InitTypeSpecs()");
config_type_specs Result = {};
config_type_spec *Root = PushTypeSpec(&Result, IDENT_NULL, FALSE);
PushTypeSpecField(Root, FT_STRING, IDENT_ASSETS_ROOT_DIR, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_ASSETS_ROOT_URL, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_BASE_DIR, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_BASE_URL, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_CACHE_DIR, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_COHOST, FALSE);
PushTypeSpecField(Root, FT_STRING, IDENT_CSS_PATH, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_DB_LOCATION, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_DEFAULT_MEDIUM, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_GENRE, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_GLOBAL_SEARCH_DIR, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_GLOBAL_SEARCH_TEMPLATE, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_GLOBAL_SEARCH_URL, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_GLOBAL_TEMPLATES_DIR, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_GLOBAL_THEME, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_GUEST, FALSE);
PushTypeSpecField(Root, FT_STRING, IDENT_HMML_DIR, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_IMAGES_PATH, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_INDEXER, FALSE);
PushTypeSpecField(Root, FT_STRING, IDENT_JS_PATH, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_LOG_LEVEL, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_NUMBERING_SCHEME, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_OWNER, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_PLAYER_LOCATION, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_PLAYER_TEMPLATE, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_QUERY_STRING, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_SEARCH_LOCATION, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_SEARCH_TEMPLATE, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_TEMPLATES_DIR, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_STREAM_USERNAME, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_VOD_PLATFORM, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_THEME, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_TITLE, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_HTML_TITLE, TRUE);
// TODO(matt): Sort out this title list stuff
//
PushTypeSpecField(Root, FT_STRING, IDENT_TITLE_LIST_DELIMITER, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_TITLE_LIST_PREFIX, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_TITLE_LIST_SUFFIX, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_TITLE_SUFFIX, TRUE);
//
////
PushTypeSpecField(Root, FT_STRING, IDENT_UNIT, TRUE);
PushTypeSpecField(Root, FT_BARE, IDENT_TITLE_LIST_END, TRUE);
PushTypeSpecField(Root, FT_BOOLEAN, IDENT_DENY_BESPOKE_TEMPLATES, TRUE);
PushTypeSpecField(Root, FT_BOOLEAN, IDENT_IGNORE_PRIVACY, TRUE);
PushTypeSpecField(Root, FT_BOOLEAN, IDENT_SINGLE_BROWSER_TAB, TRUE);
PushTypeSpecField(Root, FT_NUMBER, IDENT_PRIVACY_CHECK_INTERVAL, TRUE);
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_PROJECT, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_SUPPORT, FALSE);
config_type_spec *Support = PushTypeSpec(&Result, IDENT_SUPPORT, FALSE);
PushTypeSpecField(Support, FT_STRING, IDENT_ICON, TRUE);
PushTypeSpecField(Support, FT_STRING, IDENT_ICON_NORMAL, TRUE);
PushTypeSpecField(Support, FT_STRING, IDENT_ICON_FOCUSED, TRUE);
PushTypeSpecField(Support, FT_STRING, IDENT_ICON_TYPE, TRUE);
PushTypeSpecField(Support, FT_STRING, IDENT_ICON_VARIANTS, TRUE);
PushTypeSpecField(Support, FT_STRING, IDENT_URL, TRUE);
config_type_spec *Include = PushTypeSpec(&Result, IDENT_INCLUDE, FALSE);
PushTypeSpecField(Include, FT_STRING, IDENT_ALLOW, FALSE);
PushTypeSpecField(Include, FT_STRING, IDENT_DENY, FALSE);
config_type_spec *Person = PushTypeSpec(&Result, IDENT_PERSON, FALSE);
PushTypeSpecField(Person, FT_STRING, IDENT_NAME, TRUE);
PushTypeSpecField(Person, FT_STRING, IDENT_HOMEPAGE, TRUE);
PushTypeSpecField(Person, FT_SCOPE, IDENT_SUPPORT, FALSE);
config_type_spec *Medium = PushTypeSpec(&Result, IDENT_MEDIUM, FALSE);
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON, TRUE);
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_NORMAL, TRUE);
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_FOCUSED, TRUE);
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_DISABLED, TRUE);
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_TYPE, TRUE);
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_VARIANTS, TRUE);
PushTypeSpecField(Medium, FT_STRING, IDENT_NAME, TRUE);
PushTypeSpecField(Medium, FT_BOOLEAN, IDENT_HIDDEN, TRUE);
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);
PushTypeSpecField(Project, FT_STRING, IDENT_DEFAULT_MEDIUM, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_GENRE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_GUEST, FALSE);
PushTypeSpecField(Project, FT_STRING, IDENT_HMML_DIR, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_INDEXER, FALSE);
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_SCHEME, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_OWNER, TRUE);
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);
PushTypeSpecField(Project, FT_STRING, IDENT_STREAM_USERNAME, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_VOD_PLATFORM, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_THEME, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_HTML_TITLE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_ART, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_ART_VARIANTS, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_ICON, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_ICON_NORMAL, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_ICON_FOCUSED, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_ICON_DISABLED, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_ICON_TYPE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_ICON_VARIANTS, TRUE);
// TODO(matt): Sort out this title list stuff
//
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE_LIST_DELIMITER, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE_LIST_PREFIX, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE_LIST_SUFFIX, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE_SUFFIX, TRUE);
//
////
PushTypeSpecField(Project, FT_STRING, IDENT_UNIT, TRUE);
PushTypeSpecField(Project, FT_BARE, IDENT_TITLE_LIST_END, TRUE);
// NOTE(matt): Modes
PushTypeSpecField(Project, FT_BOOLEAN, IDENT_DENY_BESPOKE_TEMPLATES, TRUE);
PushTypeSpecField(Project, FT_BOOLEAN, IDENT_IGNORE_PRIVACY, TRUE);
PushTypeSpecField(Project, FT_BOOLEAN, IDENT_SINGLE_BROWSER_TAB, TRUE);
//
PushTypeSpecField(Project, FT_SCOPE, IDENT_INCLUDE, FALSE);
PushTypeSpecField(Project, FT_SCOPE, IDENT_MEDIUM, FALSE);
PushTypeSpecField(Project, FT_SCOPE, IDENT_PROJECT, FALSE);
return Result;
}
void
PrintTypeField(config_type_field *F, config_identifier_id ParentScopeID, int IndentationLevel)
{
IndentedCarriageReturn(IndentationLevel);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[F->ID].String);
PrintC(CS_BLACK_BOLD, " [");
if(F->Type == FT_NUMBER)
{
PrintC(CS_BLUE_BOLD, FieldTypeNames[F->Type]);
}
else if(F->Type == FT_BARE)
{
PrintC(CS_BLUE, FieldTypeNames[F->Type]);
}
else{
Colourise(CS_GREEN_BOLD);
fprintf(stderr, "\"%s\"", FieldTypeNames[F->Type]);
Colourise(CS_END);
}
PrintC(CS_BLACK_BOLD, "");
if(F->Singleton)
{
PrintC(CS_CYAN, "single");
}
else
{
PrintC(CS_MAGENTA, "multi");
}
PrintC(CS_BLACK_BOLD, "]");
// TODO(matt): Consider preventing the description from being written if it had already been
// written for a higher-level scope
if(ConfigIdentifiers[F->ID].IdentifierDescription)
{
if((F->ID == IDENT_ICON || F->ID == IDENT_NAME) && ParentScopeID == IDENT_MEDIUM)
{
if(!ConfigIdentifiers[F->ID].IdentifierDescription_MediumDisplayed)
{
++IndentationLevel;
IndentedCarriageReturn(IndentationLevel);
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[F->ID].IdentifierDescription_Medium));
ConfigIdentifiers[F->ID].IdentifierDescription_MediumDisplayed = TRUE;
--IndentationLevel;
}
}
else
{
if(!ConfigIdentifiers[F->ID].IdentifierDescriptionDisplayed)
{
++IndentationLevel;
IndentedCarriageReturn(IndentationLevel);
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[F->ID].IdentifierDescription));
ConfigIdentifiers[F->ID].IdentifierDescriptionDisplayed = TRUE;
--IndentationLevel;
}
}
}
}
void
PrintTypeSpec(config_type_spec *S, int IndentationLevel)
{
if(S->ID != IDENT_NULL)
{
IndentedCarriageReturn(IndentationLevel);
++IndentationLevel;
PrintStringC(CS_CYAN, Wrap0(ConfigIdentifiers[S->ID].String));
}
if(ConfigIdentifiers[S->ID].IdentifierDescription && !ConfigIdentifiers[S->ID].IdentifierDescriptionDisplayed)
{
IndentedCarriageReturn(IndentationLevel);
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[S->ID].IdentifierDescription));
ConfigIdentifiers[S->ID].IdentifierDescriptionDisplayed = TRUE;
}
for(int i = 0; i < S->FieldCount; ++i)
{
if(!(S->ID == IDENT_NULL && S->Field[i].Type == FT_SCOPE))
{
PrintTypeField(&S->Field[i], S->ID, IndentationLevel);
}
}
if(S->ID != IDENT_NULL) { --IndentationLevel; }
}
void
PrintTypeSpecs(config_type_specs *S, int IndentationLevel)
{
fprintf(stderr, "\n");
// TODO(matt): Document the fact that scopes within scopes are fully configurable, even though
// we only print out the "id" of scopes, omitting their containing fields
for(int i = 0; i < S->Count; ++i)
{
PrintTypeSpec(&S->Spec[i], IndentationLevel);
}
}
void
FreeTypeSpec(config_type_spec *S)
{
FreeAndResetCount(S->Field, S->FieldCount);
}
void
FreeTypeSpecs(config_type_specs *S)
{
for(int i = 0; i < S->Count; ++i)
{
FreeTypeSpec(&S->Spec[i]);
}
FreeAndResetCount(S->Spec, S->Count);
}
typedef struct
{
char *Filename;
uint64_t LineNumber;
} token_position;
typedef struct
{
config_identifier_id Key;
string Value;
token_position Position;
} config_pair;
typedef struct
{
config_identifier_id Key;
uint64_t Value;
token_position Position;
} config_int_pair;
typedef struct
{
config_identifier_id Key;
bool Value;
token_position Position;
} config_bool_pair;
typedef struct scope_tree
{
config_pair ID;
uint64_t TreeCount;
struct scope_tree *Trees;
uint64_t PairCount;
config_pair *Pairs;
uint64_t IntPairCount;
config_int_pair *IntPairs;
uint64_t BoolPairCount;
config_bool_pair *BoolPairs;
struct scope_tree *Parent;
config_type_spec TypeSpec;
//token_position Position; // NOTE(matt): We chose not to make ScopeTokens() set this value
} scope_tree;
config_type_spec *
GetTypeSpec(config_type_specs *S, config_identifier_id ID)
{
for(int i = 0; i < S->Count; ++i)
{
if(S->Spec[i].ID == ID)
{
return &S->Spec[i];
}
}
return 0;
}
void
SetTypeSpec(scope_tree *Type, config_type_specs *TypeSpecs)
{
config_type_spec *Spec = GetTypeSpec(TypeSpecs, Type->ID.Key);
//PrintTypeSpec(Spec);
Type->TypeSpec.ID = Spec->ID;
Type->TypeSpec.Permeable = Spec->Permeable;
Type->TypeSpec.FieldCount = Spec->FieldCount;
Type->TypeSpec.Field = malloc(Type->TypeSpec.FieldCount * sizeof(config_type_field));
for(int i = 0; i < Type->TypeSpec.FieldCount; ++i)
{
Type->TypeSpec.Field[i].Type = Spec->Field[i].Type;
Type->TypeSpec.Field[i].ID = Spec->Field[i].ID;
Type->TypeSpec.Field[i].Singleton = Spec->Field[i].Singleton;
Type->TypeSpec.Field[i].IsSetLocally = Spec->Field[i].IsSetLocally;
}
}
config_type_field *
CurrentFieldIsInSpec(config_type_spec *Spec, tokens *T)
{
config_type_field *Result = 0;
if(Spec)
{
for(int i = 0; i < Spec->FieldCount; ++i)
{
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
if(!StringsDifferLv0(This->Content, ConfigIdentifiers[Spec->Field[i].ID].String))
{
Result = &Spec->Field[i];
break;
}
}
}
return Result;
}
// TODO(matt): Replace with a memory_book
typedef struct
{
uint64_t Count;
string *String;
} string_list;
typedef struct
{
string Filename;
string_list Allow;
string_list Deny;
} config_include;
void
PushString(string_list *Dest, string *Src)
{
Dest->String = Fit(Dest->String, sizeof(*Dest->String), Dest->Count, 4, FALSE);
Dest->String[Dest->Count] = *Src;
++Dest->Count;
}
bool
ParseMultiStringAssignment(tokens *T, string_list *Dest, config_type_field *Field)
{
++T->CurrentIndex;
token Token = {};
if(!ExpectToken(T, TOKEN_ASSIGN, &Token))
{
return FALSE;
}
if(!ExpectToken(T, TOKEN_STRING, &Token))
{
return FALSE;
}
PushString(Dest, &Token.Content);
Field->IsSetLocally = TRUE;
if(!ExpectToken(T, TOKEN_SEMICOLON, &Token))
{
return FALSE;
}
return TRUE;
}
void
PrintCurrentToken(colour_code C, tokens *T)
{
Colourise(C);
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
PrintToken(This, T->CurrentIndex);
Colourise(CS_END);
}
bool
DepartAssignment(tokens *T)
{
uint64_t ScopeLevel = 0;
do {
if(TokenIs(T, TOKEN_IDENTIFIER))
{
++T->CurrentIndex;
if(!ExpectToken(T, TOKEN_ASSIGN, 0))
{
return FALSE;
}
if(TokenIs(T, TOKEN_STRING) || TokenIs(T, TOKEN_NUMBER))
{
++T->CurrentIndex;
}
else
{
ConfigErrorExpectation(T, TOKEN_STRING, TOKEN_NUMBER);
return FALSE;
}
if(TokenIs(T, TOKEN_SEMICOLON))
{
++T->CurrentIndex;
}
else if(TokenIs(T, TOKEN_OPEN_BRACE))
{
++ScopeLevel;
++T->CurrentIndex;
}
else
{
ConfigErrorExpectation(T, TOKEN_SEMICOLON, TOKEN_OPEN_BRACE);
return FALSE;
}
}
else if(TokenIs(T, TOKEN_CLOSE_BRACE))
{
--ScopeLevel;
++T->CurrentIndex;
}
else
{
ConfigErrorExpectation(T, TOKEN_IDENTIFIER, TOKEN_CLOSE_BRACE);
return FALSE;
}
} while(ScopeLevel > 0);
return TRUE;
}
bool
DepartIncludeAssignment(tokens *T, uint64_t IncludeIdentifierTokenIndex)
{
T->CurrentIndex = IncludeIdentifierTokenIndex;
return(DepartAssignment(T));
}
void
FreeString(string *S)
{
Free(S->Base);
S->Length = 0;
}
void
FreeStringList(string_list *S)
{
FreeAndResetCount(S->String, S->Count);
}
void
FreeInclude(config_include *I)
{
FreeStringList(&I->Allow);
FreeStringList(&I->Deny);
free(I);
I = 0;
}
bool
IsStringEmpty(string *S)
{
return S ? S->Length == 0 : TRUE;
}
// TODO(matt): This never gets called. Probably remove
void
PrintInclude(config_include *I)
{
PrintStringC(CS_YELLOW_BOLD, I->Filename);
fprintf(stderr, "\n AllowCount: %lu", I->Allow.Count);
fprintf(stderr, "\n DenyCount: %lu", I->Deny.Count);
#if 1
for(int i = 0; i < I->Allow.Count; ++i)
{
fprintf(stderr, "\n Allow: ");
PrintStringC(CS_GREEN, I->Allow.String[i]);
}
for(int i = 0; i < I->Deny.Count; ++i)
{
fprintf(stderr, "\n Deny: ");
PrintStringC(CS_GREEN, I->Deny.String[i]);
}
#endif
fprintf(stderr, "\n");
}
void
FreeScopeTreeRecursively(scope_tree *T)
{
FreeAndResetCount(T->Pairs, T->PairCount);
FreeAndResetCount(T->IntPairs, T->IntPairCount);
FreeAndResetCount(T->BoolPairs, T->BoolPairCount);
Free(T->TypeSpec.Field);
//Free(T->Position.Filename);
for(int i = 0; i < T->TreeCount; ++i)
{
FreeScopeTreeRecursively(&T->Trees[i]);
}
FreeAndResetCount(T->Trees, T->TreeCount);
}
void
FreeScopeTree(scope_tree *T)
{
FreeAndResetCount(T->Pairs, T->PairCount);
FreeAndResetCount(T->IntPairs, T->IntPairCount);
FreeAndResetCount(T->BoolPairs, T->BoolPairCount);
Free(T->TypeSpec.Field);
//Free(T->Position.Filename);
for(int i = 0; i < T->TreeCount; ++i)
{
FreeScopeTreeRecursively(&T->Trees[i]);
}
FreeAndResetCount(T->Trees, T->TreeCount);
Free(T);
}
void
PrintPair(config_pair *P, char *Delimiter, bool FillSyntax, uint64_t Indentation, bool PrependNewline, bool AppendNewline)
{
if(PrependNewline) { fprintf(stderr, "\n"); }
Indent(Indentation);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[P->Key].String);
fprintf(stderr, "%s", Delimiter);
if(P->Value.Length)
{
Colourise(CS_GREEN_BOLD);
if(FillSyntax) { fprintf(stderr, "\""); }
PrintString(P->Value);
if(FillSyntax) { fprintf(stderr, "\""); }
Colourise(CS_END);
if(FillSyntax) { fprintf(stderr, ";"); }
}
else
{
PrintC(CS_YELLOW, "[unset]");
}
if(AppendNewline) { fprintf(stderr, "\n"); }
}
void
PrintScopeID(config_pair *P, char *Delimiter, uint64_t Indentation)
{
fprintf(stderr, "\n");
Indent(Indentation);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[P->Key].String);
fprintf(stderr, "%s", Delimiter);
if(P->Value.Length)
{
Colourise(CS_GREEN_BOLD);
fprintf(stderr, "\"");
PrintString(P->Value);
fprintf(stderr, "\"");
Colourise(CS_END);
IndentedCarriageReturn(Indentation);
fprintf(stderr, "{");
}
else
{
PrintC(CS_YELLOW, "[unset]");
}
//fprintf(stderr, "\n");
}
void
PrintIntPair(config_int_pair *P, char *Delimiter, bool FillSyntax, uint64_t Indentation, bool PrependNewline, bool AppendNewline)
{
if(PrependNewline) { fprintf(stderr, "\n"); }
Indent(Indentation);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[P->Key].String);
fprintf(stderr, "%s", Delimiter);
switch(P->Key)
{
case IDENT_GENRE:
{
Colourise(CS_GREEN_BOLD);
if(FillSyntax) { fprintf(stderr, "\""); }
fprintf(stderr, "%s", GenreStrings[P->Value]);
if(FillSyntax) { fprintf(stderr, "\""); }
} break;
case IDENT_LOG_LEVEL:
{
Colourise(CS_GREEN_BOLD);
if(FillSyntax) { fprintf(stderr, "\""); }
fprintf(stderr, "%s", LogLevelStrings[P->Value]);
if(FillSyntax) { fprintf(stderr, "\""); }
} break;
case IDENT_NUMBERING_SCHEME:
{
Colourise(CS_GREEN_BOLD);
if(FillSyntax) { fprintf(stderr, "\""); }
fprintf(stderr, "%s", NumberingSchemeStrings[P->Value]);
if(FillSyntax) { fprintf(stderr, "\""); }
} break;
default:
{
Colourise(CS_BLUE_BOLD);
fprintf(stderr, "%lu", P->Value);
} break;
}
Colourise(CS_END);
if(FillSyntax) { fprintf(stderr, ";"); }
if(AppendNewline) { fprintf(stderr, "\n"); }
}
void
PrintBool(char *Title, bool Bool, char *Delimiter, bool FillSyntax, uint64_t Indentation, bool PrependNewline, bool AppendNewline)
{
if(PrependNewline) { fprintf(stderr, "\n"); }
Indent(Indentation);
PrintC(CS_YELLOW_BOLD, Title);
fprintf(stderr, "%s", Delimiter);
if(Bool == TRUE)
{
Colourise(CS_GREEN);
if(FillSyntax) { fprintf(stderr, "\""); }
fprintf(stderr, "true");
if(FillSyntax) { fprintf(stderr, "\""); }
}
else
{
Colourise(CS_RED);
if(FillSyntax) { fprintf(stderr, "\""); }
fprintf(stderr, "false");
if(FillSyntax) { fprintf(stderr, "\""); }
}
Colourise(CS_END);
if(FillSyntax) { fprintf(stderr, ";"); }
if(AppendNewline) { fprintf(stderr, "\n"); }
}
void
PrintBoolPair(config_bool_pair *P, char *Delimiter, bool FillSyntax, uint64_t Indentation, bool PrependNewline, bool AppendNewline)
{
PrintBool(ConfigIdentifiers[P->Key].String, P->Value, Delimiter, FillSyntax, Indentation, PrependNewline, AppendNewline);
}
void PrintScopeTree(scope_tree *T, int IndentationLevel);
void
PrintScopeTree(scope_tree *T, int IndentationLevel)
{
if(T)
{
if(T->ID.Key != IDENT_NULL)
{
PrintScopeID(&T->ID, " = ", IndentationLevel);
++IndentationLevel;
}
bool ShouldPrependNewline = TRUE;
bool ShouldFillSyntax = TRUE;
for(int i = 0; i < T->PairCount; ++i)
{
PrintPair(&T->Pairs[i], " = ", ShouldFillSyntax, IndentationLevel, ShouldPrependNewline, FALSE);
}
for(int i = 0; i < T->IntPairCount; ++i)
{
PrintIntPair(&T->IntPairs[i], " = ", ShouldFillSyntax, IndentationLevel, ShouldPrependNewline, FALSE);
}
for(int i = 0; i < T->BoolPairCount; ++i)
{
PrintBoolPair(&T->BoolPairs[i], " = ", ShouldFillSyntax, IndentationLevel, ShouldPrependNewline, FALSE);
}
for(int i = 0; i < T->TreeCount; ++i)
{
PrintScopeTree(&T->Trees[i], IndentationLevel);
}
if(T->ID.Key != IDENT_NULL)
{
--IndentationLevel;
IndentedCarriageReturn(IndentationLevel);
fprintf(stderr, "}");
}
}
}
void
PushPair(scope_tree *Parent, config_pair *P)
{
Parent->Pairs = Fit(Parent->Pairs, sizeof(*Parent->Pairs), Parent->PairCount, 4, FALSE);
Parent->Pairs[Parent->PairCount] = *P;
++Parent->PairCount;
}
void
PushIntPair(scope_tree *Parent, config_int_pair *I)
{
Parent->IntPairs = Fit(Parent->IntPairs, sizeof(*Parent->IntPairs), Parent->IntPairCount, 4, FALSE);
Parent->IntPairs[Parent->IntPairCount] = *I;
++Parent->IntPairCount;
}
void
PushBoolPair(scope_tree *Parent, config_bool_pair *B)
{
Parent->BoolPairs = Fit(Parent->BoolPairs, sizeof(*Parent->BoolPairs), Parent->BoolPairCount, 4, FALSE);
Parent->BoolPairs[Parent->BoolPairCount] = *B;
++Parent->BoolPairCount;
}
scope_tree *
PushScope(config_type_specs *TypeSpecs, scope_tree *Parent, config_pair *ID)
{
// TODO(matt): I reckon what's going on here is that a scope_tree's Parent pointer gets invalidated when that scope_tree's
// Grandparent needs to reallocate its Trees pointer.
//
// So to fix it we have two options:
// 1. When realloc'ing, loop over the Trees, offsetting the Trees->Parent pointer of each Tree's child
// 2. Store this whole stuff in a memory_book-type of thing, so that pointers never get invalidated
//
// We did Option 1, but think we probably ought to use Option 2 instead. Doing so will save us having to
// remember if anything points into our memory, and to offset those pointers if so
scope_tree *Current = Parent->Trees;
Parent->Trees = Fit(Parent->Trees, sizeof(*Parent->Trees), Parent->TreeCount, 4, TRUE);
int64_t Diff = Parent->Trees - Current;
if(Diff)
{
for(int i = 0; i < Parent->TreeCount; ++i)
{
scope_tree *Child = Parent->Trees + i;
for(int j = 0; j < Child->TreeCount; ++j)
{
Child->Trees[j].Parent += Diff;
}
}
}
Parent->Trees[Parent->TreeCount].ID = *ID;
SetTypeSpec(&Parent->Trees[Parent->TreeCount], TypeSpecs);
Parent->Trees[Parent->TreeCount].Parent = Parent;
++Parent->TreeCount;
return(&Parent->Trees[Parent->TreeCount - 1]);
}
config_identifier_id
GetConfigIdentifierFromToken(token *T)
{
for(config_identifier_id i = 0; i < IDENT_COUNT; ++i)
{
if(!StringsDifferLv0(T->Content, ConfigIdentifiers[i].String))
{
return i;
}
}
return IDENT_NULL;
}
config_identifier_id
GetConfigIdentifierFromString(string S)
{
for(config_identifier_id i = 0; i < IDENT_COUNT; ++i)
{
if(!StringsDifferLv0(S, ConfigIdentifiers[i].String))
{
return i;
}
}
return IDENT_NULL;
}
config_type_field *
FieldIsInSpec(config_type_spec *Spec, config_identifier_id ID)
{
for(int i = 0; i < Spec->FieldCount; ++i)
{
if(Spec->Field[i].ID == ID)
{
return &Spec->Field[i];
}
}
return 0;
}
bool
PairsMatch(config_pair *A, config_pair *B)
{
return A->Key == B->Key && StringsMatch(A->Value, B->Value);
}
scope_tree *
GetScope(scope_tree *T, config_pair *ID, bool Singleton)
{
for(int i = 0; i < T->TreeCount; ++i)
{
if(Singleton)
{
if(T->Trees[i].ID.Key == ID->Key)
{
return &T->Trees[i];
}
}
else
{
if(PairsMatch(&T->Trees[i].ID, ID))
{
return &T->Trees[i];
}
}
}
return 0;
}
config_pair *
GetAssignment(scope_tree *T, config_pair *Assignment, bool Singleton)
{
for(int i = 0; i < T->PairCount; ++i)
{
if(Singleton)
{
if(T->Pairs[i].Key == Assignment->Key)
{
return &T->Pairs[i];
}
}
else
{
if(PairsMatch(&T->Pairs[i], Assignment))
{
return &T->Pairs[i];
}
}
}
return 0;
}
config_int_pair *
GetIntAssignment(scope_tree *T, config_int_pair *Assignment, bool Singleton)
{
for(int i = 0; i < T->IntPairCount; ++i)
{
if(T->IntPairs[i].Key == Assignment->Key)
{
if(Singleton || T->IntPairs[i].Value == Assignment->Value)
{
return &T->IntPairs[i];
}
}
}
return 0;
}
config_bool_pair *
GetBoolAssignment(scope_tree *T, config_bool_pair *Assignment, bool Singleton)
{
for(int i = 0; i < T->BoolPairCount; ++i)
{
if(T->BoolPairs[i].Key == Assignment->Key)
{
if(Singleton || T->BoolPairs[i].Value == Assignment->Value)
{
return &T->BoolPairs[i];
}
}
}
return 0;
}
scope_tree *CopyScope(config_type_specs *TypeSpecs, scope_tree *Dest, scope_tree *Src);
void
CopyScopeContents(config_type_specs *TypeSpecs, scope_tree *Dest, scope_tree *Src)
{
for(int i = 0; i < Src->PairCount; ++i)
{
PushPair(Dest, &Src->Pairs[i]);
}
for(int i = 0; i < Src->IntPairCount; ++i)
{
PushIntPair(Dest, &Src->IntPairs[i]);
}
for(int i = 0; i < Src->BoolPairCount; ++i)
{
PushBoolPair(Dest, &Src->BoolPairs[i]);
}
for(int i = 0; i < Src->TreeCount; ++i)
{
CopyScope(TypeSpecs, Dest, &Src->Trees[i]);
}
}
scope_tree *
CopyScope(config_type_specs *TypeSpecs, scope_tree *Dest, scope_tree *Src)
{
Dest = PushScope(TypeSpecs, Dest, &Src->ID);
CopyScopeContents(TypeSpecs, Dest, Src);
return Dest;
}
void
MergeScopes(config_type_specs *TypeSpecs, scope_tree *Dest, scope_tree *Src, bool Intergenerational)
{
if(!Intergenerational || Dest->TypeSpec.Permeable)
{
for(int i = 0; i < Src->PairCount; ++i)
{
config_type_field *Field = FieldIsInSpec(&Dest->TypeSpec, Src->Pairs[i].Key);
if(Field)
{
config_pair *ThisAssignment = GetAssignment(Dest, &Src->Pairs[i], Field->Singleton);
if(!ThisAssignment) { PushPair(Dest, &Src->Pairs[i]); }
}
}
for(int i = 0; i < Src->IntPairCount; ++i)
{
config_type_field *Field = FieldIsInSpec(&Dest->TypeSpec, Src->IntPairs[i].Key);
if(Field)
{
config_int_pair *ThisAssignment = GetIntAssignment(Dest, &Src->IntPairs[i], Field->Singleton);
if(!ThisAssignment) { PushIntPair(Dest, &Src->IntPairs[i]); }
}
}
for(int i = 0; i < Src->BoolPairCount; ++i)
{
config_type_field *Field = FieldIsInSpec(&Dest->TypeSpec, Src->BoolPairs[i].Key);
if(Field)
{
config_bool_pair *ThisAssignment = GetBoolAssignment(Dest, &Src->BoolPairs[i], Field->Singleton);
if(!ThisAssignment) { PushBoolPair(Dest, &Src->BoolPairs[i]); }
}
}
for(int i = 0; i < Src->TreeCount; ++i)
{
config_type_field *Field = FieldIsInSpec(&Dest->TypeSpec, Src->Trees[i].ID.Key);
if(Field && !(Intergenerational && Src->Trees[i].ID.Key == Dest->ID.Key))
{
scope_tree *ThisAssignment = GetScope(Dest, &Src->Trees[i].ID, Field->Singleton);
if(!ThisAssignment) { CopyScope(TypeSpecs, Dest, &Src->Trees[i]); }
}
}
}
}
typedef enum
{
CRT_NULL,
CRT_ALLOW,
CRT_DENY,
} config_rule_type;
typedef struct
{
config_rule_type Scheme;
uint64_t Count;
config_identifier_id *ID;
} config_include_rules;
void
PushRule(config_include_rules *R, config_identifier_id ID)
{
for(int i = 0; i < R->Count; ++i)
{
if(ID == R->ID[i])
{
return;
}
}
R->ID = Fit(R->ID, sizeof(*R->ID), R->Count, 8, FALSE);
R->ID[R->Count] = ID;
++R->Count;
}
void
FreeRules(config_include_rules *R)
{
FreeAndResetCount(R->ID, R->Count);
}
void
ParseRuleString(char *Filename, config_type_specs *TypeSpecs, config_type_spec *ParentTypeSpec, config_include_rules *Rules, token *RuleString)
{
int i = SkipWhitespaceS(&RuleString->Content, 0);
config_type_field *Field = 0;
for(; i < RuleString->Content.Length; i = SkipWhitespaceS(&RuleString->Content, i))
{
if(IsValidIdentifierCharacter(RuleString->Content.Base[i]))
{
string S = {};
S.Base = RuleString->Content.Base + i;
for(; i < RuleString->Content.Length && IsValidIdentifierCharacter(RuleString->Content.Base[i]); ++i)
{
++S.Length;
}
config_identifier_id ID;
if((ID = GetConfigIdentifierFromString(S)) != IDENT_NULL)
{
if((Field = FieldIsInSpec(ParentTypeSpec, ID)))
{
PushRule(Rules, ID);
if(i == RuleString->Content.Length && (ParentTypeSpec = GetTypeSpec(TypeSpecs, Field->ID)))
{
for(int j = 0; j < ParentTypeSpec->FieldCount; ++j)
{
PushRule(Rules, ParentTypeSpec->Field[j].ID);
config_type_spec *ThisTypeSpec = GetTypeSpec(TypeSpecs, ParentTypeSpec->Field[j].ID);
if(ThisTypeSpec)
{
for(int k = 0; k < ThisTypeSpec->FieldCount; ++k)
{
PushRule(Rules, ThisTypeSpec->Field[k].ID);
}
}
}
}
}
else
{
ConfigError(Filename, RuleString->LineNumber, S_WARNING, "Field not in spec: ", &S);
PrintTypeSpec(ParentTypeSpec, 0);
return;
}
}
else
{
ConfigError(Filename, RuleString->LineNumber, S_WARNING, "Unknown identifier: ", &S);
return;
}
}
else if(RuleString->Content.Base[i] == '.')
{
if(Field)
{
ParentTypeSpec = GetTypeSpec(TypeSpecs, Field->ID);
++i;
if(!ParentTypeSpec)
{
string This = Wrap0(ConfigIdentifiers[Field->ID].String);
ConfigError(Filename, RuleString->LineNumber, S_WARNING, "Invalid permission, type has no members: ", &This);
}
}
else
{
ConfigError(Filename, RuleString->LineNumber, S_WARNING, "Malformed rule string. \".\" must be preceded by a valid type", 0);
return;
}
}
else
{
string Char = GetUTF8Character(RuleString->Content.Base + i, RuleString->Content.Length - i);
ConfigError(Filename, RuleString->LineNumber, S_WARNING, "Invalid character in include rule: ", &Char);
return;
}
}
}
rc
ParseIncludeRules(config_type_specs *TypeSpecs, config_type_spec *ParentTypeSpec, tokens *T, config_include_rules *Rules)
{
uint64_t IncludeIdentifierTokenIndex = T->CurrentIndex - 3;
if(TokenIs(T, TOKEN_SEMICOLON))
{
++T->CurrentIndex;
return RC_SUCCESS;
}
else if(TokenIs(T, TOKEN_OPEN_BRACE))
{
++T->CurrentIndex;
for(; T->CurrentIndex < T->Token.ItemCount;)
{
if(TokenIs(T, TOKEN_IDENTIFIER))
{
if(TokenEquals(T, "allow"))
{
if(Rules->Scheme == CRT_DENY)
{
FreeRules(Rules);
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Mixture of allow and deny identifiers in \"include\" scope", 0);
return DepartIncludeAssignment(T, IncludeIdentifierTokenIndex) ? RC_SCHEME_MIXTURE : RC_SYNTAX_ERROR;
}
Rules->Scheme = CRT_ALLOW;
}
else if(TokenEquals(T, "deny"))
{
if(Rules->Scheme == CRT_ALLOW)
{
FreeRules(Rules);
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Mixture of allow and deny identifiers in \"include\" scope", 0);
return DepartIncludeAssignment(T, IncludeIdentifierTokenIndex) ? RC_SCHEME_MIXTURE : RC_SYNTAX_ERROR;
}
Rules->Scheme = CRT_DENY;
}
else
{
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Invalid identifier in \"include\" scope: ", &This->Content);
fprintf(stderr,
" Valid identifiers:\n"
" allow\n"
" deny\n");
FreeRules(Rules);
return DepartIncludeAssignment(T, IncludeIdentifierTokenIndex) ? RC_INVALID_IDENTIFIER : RC_SYNTAX_ERROR;
}
++T->CurrentIndex;
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeRules(Rules); return RC_SYNTAX_ERROR; }
token RuleString = {};
if(!ExpectToken(T, TOKEN_STRING, &RuleString)) { FreeRules(Rules); return RC_SYNTAX_ERROR; }
ParseRuleString(T->File.Path, TypeSpecs, ParentTypeSpec, Rules, &RuleString);
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeRules(Rules); return RC_SYNTAX_ERROR; }
}
else if(TokenIs(T, TOKEN_CLOSE_BRACE))
{
++T->CurrentIndex;
return RC_SUCCESS;
}
else
{
FreeRules(Rules);
return RC_SYNTAX_ERROR;
}
}
}
else
{
FreeRules(Rules);
ConfigErrorExpectation(T, TOKEN_SEMICOLON, TOKEN_OPEN_BRACE);
return RC_SYNTAX_ERROR;
}
return RC_SUCCESS;
}
bool
FieldIsPermitted(config_include_rules *Rules, config_type_field *Field)
{
for(int i = 0; i < Rules->Count; ++i)
{
if(Field->ID == Rules->ID[i])
{
return Rules->Scheme == CRT_ALLOW ? TRUE : FALSE;
}
}
return Rules->Scheme == CRT_ALLOW ? FALSE : TRUE;
}
void
ResetFields(config_type_spec *S)
{
for(int i = 0; i < S->FieldCount; ++i)
{
S->Field[i].IsSetLocally = FALSE;
}
}
scope_tree *
PushDefaultScope(config_type_specs *TypeSpecs, scope_tree *Parent, config_identifier_id Key, string Value)
{
config_pair ID = { .Key = Key, .Value = Value };
return PushScope(TypeSpecs, Parent, &ID);
}
void
PushDefaultPair(scope_tree *Parent, config_identifier_id Key, string Value)
{
config_pair Pair = { .Key = Key, .Value = Value };
return PushPair(Parent, &Pair);
}
void
PushDefaultIntPair(scope_tree *Parent, config_identifier_id Key, uint64_t Value)
{
config_int_pair IntPair = { .Key = Key, .Value = Value };
return PushIntPair(Parent, &IntPair);
}
#define DEFAULT_PRIVACY_CHECK_INTERVAL 60 * 4
void
SetDefaults(scope_tree *Root, config_type_specs *TypeSpecs)
{
scope_tree *SupportPatreon = PushDefaultScope(TypeSpecs, Root, IDENT_SUPPORT, Wrap0("patreon"));
PushDefaultPair(SupportPatreon, IDENT_ICON, Wrap0("cinera_sprite_patreon.png"));
PushDefaultIntPair(SupportPatreon, IDENT_ICON_TYPE, IT_GRAPHICAL);
PushDefaultIntPair(SupportPatreon, IDENT_ICON_VARIANTS, GetArtVariantFromString(Wrap0("normal")));
PushDefaultPair(SupportPatreon, IDENT_URL, Wrap0("https://patreon.com/$person"));
scope_tree *SupportSendOwl = PushDefaultScope(TypeSpecs, Root, IDENT_SUPPORT, Wrap0("sendowl"));
PushDefaultPair(SupportSendOwl, IDENT_ICON, Wrap0("cinera_sprite_sendowl.png"));
scope_tree *MediumAuthored = PushDefaultScope(TypeSpecs, Root, IDENT_MEDIUM, Wrap0("authored"));
PushDefaultPair(MediumAuthored, IDENT_ICON, Wrap0("&#128490;"));
PushDefaultPair(MediumAuthored, IDENT_NAME, Wrap0("Chat Comment"));
PushDefaultPair(Root, IDENT_QUERY_STRING, Wrap0("r"));
PushDefaultPair(Root, IDENT_THEME, Wrap0("$origin"));
PushDefaultPair(Root, IDENT_CACHE_DIR, Wrap0("$XDG_CACHE_HOME/cinera"));
PushDefaultPair(Root, IDENT_DB_LOCATION, Wrap0("$XDG_CONFIG_HOME/cinera/cinera.db"));
// TODO(matt): Consider where the genre setting should apply, project vs entry level
PushDefaultIntPair(Root, IDENT_GENRE, GENRE_VIDEO);
PushDefaultIntPair(Root, IDENT_NUMBERING_SCHEME, NS_LINEAR);
PushDefaultIntPair(Root, IDENT_PRIVACY_CHECK_INTERVAL, DEFAULT_PRIVACY_CHECK_INTERVAL);
PushDefaultIntPair(Root, IDENT_LOG_LEVEL, LOG_ERROR);
}
scope_tree *
ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, config_type_specs *TypeSpecs, config_include_rules *Rules)
{
scope_tree *Parent = Tree;
for(T->CurrentIndex = 0; T->CurrentIndex < T->Token.ItemCount;)
{
if(TokenIs(T, TOKEN_IDENTIFIER))
{
config_type_field *Field = CurrentFieldIsInSpec(&Parent->TypeSpec, T);
if(Field)
{
if(!Rules || FieldIsPermitted(Rules, Field))
{
if(Field->IsSetLocally && Field->Singleton)
{
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Field already set: ", &This->Content);
}
// NOTE(matt): If we get rid of FT_BARE, then the ++T->CurrentIndex that all cases perform could happen
// right here
config_pair Pair = {};
config_int_pair IntPair = {};
config_bool_pair BoolPair = {};
Pair.Key = IntPair.Key = BoolPair.Key = Field->ID;
Pair.Position.Filename = IntPair.Position.Filename = BoolPair.Position.Filename = T->File.Path;
switch(Field->Type)
{
case FT_BARE:
{
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
token Value = *This;
Pair.Position.LineNumber = Value.LineNumber;
++T->CurrentIndex;
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
config_pair *Assignment = 0;
if(!(Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
{
PushPair(Parent, &Pair);
}
} break;
case FT_BOOLEAN:
{
++T->CurrentIndex;
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeScopeTree(Tree); return 0; }
token Value = {};
if(!ExpectToken(T, TOKEN_STRING, &Value)) { FreeScopeTree(Tree); return 0; }
BoolPair.Position.LineNumber = Value.LineNumber;
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
bool Bool = GetBoolFromString(T->File.Path, &Value);
if(Bool != -1)
{
BoolPair.Value = Bool;
config_bool_pair *Assignment = 0;
if((Assignment = GetBoolAssignment(Parent, &BoolPair, Field->Singleton)))
{
Assignment->Value = BoolPair.Value;
}
else
{
PushBoolPair(Parent, &BoolPair);
}
}
else { FreeScopeTree(Tree); return 0; }
} break;
case FT_STRING:
{
++T->CurrentIndex;
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeScopeTree(Tree); return 0; }
token Value = {};
if(!ExpectToken(T, TOKEN_STRING, &Value)) { FreeScopeTree(Tree); return 0; }
Pair.Position.LineNumber = Value.LineNumber;
IntPair.Position.LineNumber = Value.LineNumber;
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
if(Field->ID == IDENT_NUMBERING_SCHEME)
{
numbering_scheme NumberingScheme = GetNumberingSchemeFromString(T->File.Path, &Value);
if(NumberingScheme != NS_COUNT)
{
IntPair.Value = NumberingScheme;
config_int_pair *Assignment = 0;
if((Assignment = GetIntAssignment(Parent, &IntPair, Field->Singleton)))
{
Assignment->Value = IntPair.Value;
}
else
{
PushIntPair(Parent, &IntPair);
}
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_LOG_LEVEL)
{
log_level LogLevel = GetLogLevelFromString(T->File.Path, &Value);
if(LogLevel != LOG_COUNT)
{
IntPair.Value = LogLevel;
config_int_pair *Assignment = 0;
if((Assignment = GetIntAssignment(Parent, &IntPair, Field->Singleton)))
{
Assignment->Value = IntPair.Value;
}
else
{
PushIntPair(Parent, &IntPair);
}
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_GENRE)
{
genre Genre = GetGenreFromString(T->File.Path, &Value);
if(Genre != GENRE_COUNT)
{
IntPair.Value = Genre;
config_int_pair *Assignment = 0;
if((Assignment = GetIntAssignment(Parent, &IntPair, Field->Singleton)))
{
Assignment->Value = IntPair.Value;
}
else
{
PushIntPair(Parent, &IntPair);
}
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_ICON_TYPE)
{
icon_type IconType = GetIconTypeFromString(T->File.Path, &Value);
if(IconType != IT_COUNT)
{
IntPair.Value = IconType;
config_int_pair *Assignment = 0;
if((Assignment = GetIntAssignment(Parent, &IntPair, Field->Singleton)))
{
Assignment->Value = IntPair.Value;
}
else
{
PushIntPair(Parent, &IntPair);
}
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_ART_VARIANTS || Field->ID == IDENT_ICON_VARIANTS)
{
int64_t ArtVariants = ParseArtVariantsString(T->File.Path, &Value);
if(ArtVariants != -1)
{
IntPair.Value = ArtVariants;
config_int_pair *Assignment = 0;
if((Assignment = GetIntAssignment(Parent, &IntPair, Field->Singleton)))
{
Assignment->Value = IntPair.Value;
}
else
{
PushIntPair(Parent, &IntPair);
}
}
else { FreeScopeTree(Tree); return 0; }
}
else
{
Pair.Value = Value.Content;
config_pair *Assignment = 0;
if((Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
{
Assignment->Value = Pair.Value;
Assignment->Position.LineNumber = Pair.Position.LineNumber;
Assignment->Position.Filename = Pair.Position.Filename;
}
else
{
PushPair(Parent, &Pair);
}
}
} break;
case FT_NUMBER:
{
++T->CurrentIndex;
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeScopeTree(Tree); return 0; }
token Value = {};
if(!ExpectToken(T, TOKEN_NUMBER, &Value)) { FreeScopeTree(Tree); return 0; }
IntPair.Value = Value.int64_t;
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
config_int_pair *Assignment = 0;
if((Assignment = GetIntAssignment(Parent, &IntPair, Field->Singleton)))
{
Assignment->Value = IntPair.Value;
}
else
{
PushIntPair(Parent, &IntPair);
}
} break;
case FT_SCOPE:
{
++T->CurrentIndex;
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeScopeTree(Tree); return 0; }
token Value = {};
if(!ExpectToken(T, TOKEN_STRING, &Value)) { FreeScopeTree(Tree); return 0; }
Pair.Value = Value.Content;
Pair.Position.LineNumber = Value.LineNumber;
if(Field->ID == IDENT_INCLUDE)
{
int IncludePathTokenIndex = T->CurrentIndex - 1;
config_include_rules Rules = {};
switch(ParseIncludeRules(TypeSpecs, &Parent->TypeSpec, T, &Rules))
{
case RC_SUCCESS:
{
string IncluderFilePath = Wrap0(T->File.Path);
char *IncludePath = ExpandPath(Pair.Value, &IncluderFilePath);
string IncludePathL = Wrap0(IncludePath);
tokens *I = 0;
for(int i = 0; i < TokensList->ItemCount; ++i)
{
tokens *This = GetPlaceInBook(TokensList, i);
if(!StringsDifferLv0(Value.Content, This->File.Path))
{
I = This;
I->CurrentIndex = 0;
I->CurrentLine = 0;
}
}
if(!I)
{
I = Tokenise(TokensList, IncludePathL);
}
if(I)
{
Parent = ScopeTokens(Parent, TokensList, I, TypeSpecs, &Rules);
FreeRules(&Rules);
if(!Parent)
{
return 0;
}
}
else
{
token *This = GetPlaceInBook(&T->Token, IncludePathTokenIndex);
ConfigFileIncludeError(T->File.Path, This->LineNumber, Wrap0(IncludePath));
}
Free(IncludePath);
} break;
case RC_SCHEME_MIXTURE:
{
} break;
case RC_SYNTAX_ERROR:
{
FreeScopeTree(Tree); return 0;
} break;
case RC_INVALID_IDENTIFIER:
{
} break;
default: break;
}
}
else
{
// TODO(matt): Here is probably where we must fix deduplication of support scopes
scope_tree *Search = 0;
bool SelfContained = FALSE;
if(Field->ID == IDENT_SUPPORT)
{
scope_tree *ParentP = Parent;
Search = GetScope(ParentP, &Pair, Field->Singleton);
if(Search) { SelfContained = TRUE; }
while(!Search && ParentP->Parent)
{
ParentP = ParentP->Parent;
Search = GetScope(ParentP, &Pair, Field->Singleton);
}
}
else
{
Search = GetScope(Parent, &Pair, Field->Singleton);
}
if(Search)
{
if(Field->ID == IDENT_SUPPORT && !SelfContained)
{
Parent = CopyScope(TypeSpecs, Parent, Search);
}
else
{
Parent = Search;
MergeScopes(TypeSpecs, Parent, Parent->Parent, TRUE);
}
}
else
{
Parent = PushScope(TypeSpecs, Parent, &Pair);
if(Parent->ID.Key == IDENT_MEDIUM)
{
config_bool_pair Hidden = {};
Hidden.Key = IDENT_HIDDEN;
Hidden.Value = FALSE;
PushBoolPair(Parent, &Hidden);
}
MergeScopes(TypeSpecs, Parent, Parent->Parent, TRUE);
}
ResetFields(&Parent->TypeSpec);
if(TokenIs(T, TOKEN_SEMICOLON))
{
Parent = Parent->Parent;
}
else if(TokenIs(T, TOKEN_OPEN_BRACE))
{
}
else
{
ConfigErrorExpectation(T, TOKEN_SEMICOLON, TOKEN_OPEN_BRACE);
FreeScopeTree(Tree); return 0;
}
++T->CurrentIndex;
}
} break;
}
// NOTE(matt): This may not be appropriate for the IDENT_INCLUDE...
Field->IsSetLocally = TRUE;
}
else
{
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Field not allowed: ", &This->Content);
DepartAssignment(T);
}
}
else
{
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Invalid field: ", &This->Content);
if(!DepartAssignment(T))
{
FreeScopeTree(Tree);
return 0;
}
}
}
else if(TokenIs(T, TOKEN_CLOSE_BRACE))
{
if(!Parent->Parent)
{
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
ConfigError(T->File.Path, This->LineNumber, S_ERROR, "Syntax error: Unpaired closing brace", 0);
FreeScopeTree(Tree);
return 0;
}
Parent = Parent->Parent;
++T->CurrentIndex;
}
else
{
ConfigErrorExpectation(T, TOKEN_IDENTIFIER, TOKEN_CLOSE_BRACE);
FreeScopeTree(Tree);
return 0;
}
}
return Tree;
}
// TODO(matt): Fully nail down the title list stuff. Perhaps, make the title_list_end; identifier somehow indicate to child
// scopes that they should not absorb any title_list stuff...
typedef struct
{
token_position Position;
config_identifier_id Variable;
} resolution_error;
typedef struct
{
uint64_t ErrorCount;
resolution_error *Errors;
uint64_t WarningCount;
resolution_error *Warnings;
} resolution_errors;
void
PushError(resolution_errors *E, severity Severity, token_position *Position, config_identifier_id Variable)
{
if(Severity == S_WARNING)
{
E->Warnings = Fit(E->Warnings, sizeof(*E->Warnings), E->WarningCount, 16, TRUE);
if(Position)
{
E->Warnings[E->WarningCount].Position = *Position;
}
E->Warnings[E->WarningCount].Variable = Variable;
++E->WarningCount;
}
else
{
E->Errors = Fit(E->Errors, sizeof(*E->Errors), E->ErrorCount, 16, TRUE);
if(Position)
{
E->Errors[E->ErrorCount].Position = *Position;
}
E->Errors[E->ErrorCount].Variable = Variable;
++E->ErrorCount;
}
}
resolution_error *
GetError(resolution_errors *E, severity Severity, token_position *Position, config_identifier_id Variable)
{
if(Severity == S_WARNING)
{
for(int i = 0; i < E->WarningCount; ++i)
{
if(E->Warnings[i].Position.Filename == Position->Filename &&
E->Warnings[i].Position.LineNumber == Position->LineNumber &&
E->Warnings[i].Variable == Variable)
{
return &E->Warnings[i];
}
}
}
else
{
for(int i = 0; i < E->ErrorCount; ++i)
{
if(E->Errors[i].Position.Filename == Position->Filename &&
E->Errors[i].Position.LineNumber == Position->LineNumber &&
E->Errors[i].Variable == Variable)
{
return &E->Errors[i];
}
}
}
return 0;
}
void
FreeErrors(resolution_errors *E)
{
FreeAndResetCount(E->Errors, E->ErrorCount);
FreeAndResetCount(E->Warnings, E->WarningCount);
}
string
ResolveEnvironmentVariable(memory_book *M, string Variable)
{
string Result = {};
int DollarCharacterBytes = 1;
int NullTerminationBytes = 1;
// NOTE(matt): Stack-string
char Variable0[DollarCharacterBytes + Variable.Length + NullTerminationBytes];
char *Ptr = Variable0;
if(!StringsMatch(Variable, Wrap0("~")))
{
*Ptr++ = '$';
}
for(int i = 0; i < Variable.Length; ++i)
{
*Ptr++ = Variable.Base[i];
}
*Ptr = '\0';
int Flags = WRDE_NOCMD | WRDE_UNDEF | WRDE_APPEND;
wordexp_t Expansions = {};
wordexp(Variable0, &Expansions, Flags);
if(Expansions.we_wordc > 0)
{
Result = ExtendStringInBook(M, Wrap0(Expansions.we_wordv[0]));
}
wordfree(&Expansions);
return Result;
}
person *
GetPerson(config *C, resolution_errors *E, config_pair *PersonID)
{
for(int i = 0; i < C->Person.ItemCount; ++i)
{
person *Person = GetPlaceInBook(&C->Person, i);
if(!StringsDifferCaseInsensitive(Person->ID, PersonID->Value))
{
return Person;
}
}
if(!GetError(E, S_WARNING, &PersonID->Position, IDENT_PERSON))
{
ConfigError(PersonID->Position.Filename, PersonID->Position.LineNumber, S_WARNING, "Could not find person: ", &PersonID->Value);
PushError(E, S_WARNING, &PersonID->Position, IDENT_PERSON);
}
return 0;
}
string
DeriveLineageOfProject(config *C, scope_tree *Project)
{
string Result = {};
string_list StringList = {};
if(Project)
{
PushString(&StringList, &Project->ID.Value);
Project = Project->Parent;
}
while(Project && Project->ID.Key == IDENT_PROJECT)
{
string Slash = Wrap0("/");
PushString(&StringList, &Slash);
PushString(&StringList, &Project->ID.Value);
Project = Project->Parent;
}
for(int i = StringList.Count - 1; i >= 0; --i)
{
Result = ExtendStringInBook(&C->ResolvedVariables, StringList.String[i]);
}
FreeStringList(&StringList);
return Result;
}
string
EmptyString(void)
{
string Result = {};
return Result;
}
string
DeriveLineageWithoutOriginOfProject(config *C, scope_tree *Project)
{
string Result = {};
string_list StringList = {};
if(Project->Parent && Project->Parent->ID.Key == IDENT_PROJECT)
{
PushString(&StringList, &Project->ID.Value);
Project = Project->Parent;
}
while(Project->Parent && Project->Parent->ID.Key == IDENT_PROJECT)
{
string Slash = Wrap0("/");
PushString(&StringList, &Slash);
PushString(&StringList, &Project->ID.Value);
Project = Project->Parent;
}
if(StringList.Count > 0)
{
for(int i = StringList.Count - 1; i >= 0; --i)
{
Result = ExtendStringInBook(&C->ResolvedVariables, StringList.String[i]);
}
FreeStringList(&StringList);
}
else
{
Result = ExtendStringInBook(&C->ResolvedVariables, EmptyString());
}
return Result;
}
string
ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_identifier_id Variable, token_position *Position)
{
string Result = {};
switch(Variable)
{
case IDENT_LINEAGE:
{
if(Scope->ID.Key == IDENT_PROJECT)
{
Result = DeriveLineageOfProject(C, Scope);
}
else
{
string This = Wrap0(ConfigIdentifiers[Variable].String);
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This);
PushError(E, S_WARNING, Position, Variable);
}
}
} break;
case IDENT_LINEAGE_WITHOUT_ORIGIN:
{
if(Scope->ID.Key == IDENT_PROJECT)
{
Result = DeriveLineageWithoutOriginOfProject(C, Scope);
}
else
{
string This = Wrap0(ConfigIdentifiers[Variable].String);
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This);
PushError(E, S_WARNING, Position, Variable);
}
}
} break;
case IDENT_ORIGIN:
{
if(Scope->ID.Key == IDENT_PROJECT)
{
while(Scope->Parent && Scope->Parent->ID.Key == IDENT_PROJECT)
{
Scope = Scope->Parent;
}
Result = ExtendStringInBook(&C->ResolvedVariables, Scope->ID.Value);
}
else
{
string This = Wrap0(ConfigIdentifiers[Variable].String);
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This);
PushError(E, S_WARNING, Position, Variable);
}
}
} break;
case IDENT_OWNER:
{
bool Processed = FALSE;
for(int i = 0; i < Scope->PairCount; ++i)
{
if(Scope->Pairs[i].Key == Variable)
{
if(GetPerson(C, E, &Scope->Pairs[i]))
{
Result = ExtendStringInBook(&C->ResolvedVariables, Scope->Pairs[i].Value);
Processed = TRUE;
break;
}
else
{
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Owner set, but the person does not exist: ", &Scope->Pairs[i].Value);
PushError(E, S_WARNING, Position, Variable);
}
Processed = TRUE;
}
}
}
if(!Processed)
{
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "No owner set", 0);
PushError(E, S_WARNING, Position, Variable);
}
}
} break;
case IDENT_PERSON:
{
if(Scope->ID.Key != Variable)
{
Scope = Scope->Parent;
}
} // NOTE(matt): Intentional fall-through
case IDENT_PROJECT:
case IDENT_SUPPORT:
{
if(Scope && Scope->ID.Key == Variable)
{
Result = ExtendStringInBook(&C->ResolvedVariables, Scope->ID.Value);
}
else
{
string This = Wrap0(ConfigIdentifiers[Variable].String);
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This);
PushError(E, S_WARNING, Position, Variable);
}
}
} break;
default:
{
if(!GetError(E, S_WARNING, Position, Variable))
{
string This = Wrap0(ConfigIdentifiers[Variable].String);
ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Unhandled local variable: ", &This);
PushError(E, S_WARNING, Position, Variable);
}
} break;
};
return Result;
}
string
ResolveString(config *C, resolution_errors *E, scope_tree *Parent, config_pair *S, bool AbsoluteFilePath)
{
//PrintFunctionName("ResolveString()");
string Result = {};
ResetPen(&C->ResolvedVariables);
for(int i = 0; i < S->Value.Length;)
{
switch(S->Value.Base[i])
{
case '$':
{
string Test = {};
++i;
if(i < S->Value.Length && S->Value.Base[i] == '{')
{
++i;
Test.Base = S->Value.Base + i;
while(i < S->Value.Length && S->Value.Base[i] != '}')
{
++Test.Length;
++i;
}
}
else
{
Test.Base = S->Value.Base + i;
while(i < S->Value.Length && IsValidIdentifierCharacter(S->Value.Base[i]))
{
++Test.Length;
++i;
}
if(i > 0) { --i; }
}
config_identifier_id ID = GetConfigIdentifierFromString(Test);
if(ID != IDENT_NULL)
{
Result = ResolveLocalVariable(C, E, Parent, ID, &S->Position);
}
else
{
Result = ResolveEnvironmentVariable(&C->ResolvedVariables, Test);
}
} break;
case '~':
{
string Char = { .Base = S->Value.Base + i, .Length = 1 };
if(AbsoluteFilePath && i == 0)
{
Result = ResolveEnvironmentVariable(&C->ResolvedVariables, Char);
}
else
{
Result = ExtendStringInBook(&C->ResolvedVariables, Char);
}
} break;
case '\\': { ++i; }; // NOTE(matt): Intentional fall-through
default:
{
string Char = { .Base = S->Value.Base + i, .Length = 1 };
Result = ExtendStringInBook(&C->ResolvedVariables, Char);
} break;
}
++i;
}
return Result;
}
typedef enum
{
P_ABS,
P_REL,
} path_relativity;
string
StripSlashes(string Path, path_relativity Relativity)
{
string Result = Path;
if(Relativity == P_REL)
{
while(Result.Length > 0 && *Result.Base == '/')
{
++Result.Base;
--Result.Length;
}
}
while(Result.Length > 0 && Result.Base[Result.Length - 1] == '/')
{
--Result.Length;
}
return Result;
}
bool
HasImageFileExtension(string Path)
{
return ExtensionMatches(Path, EXT_GIF) ||
ExtensionMatches(Path, EXT_JPEG) ||
ExtensionMatches(Path, EXT_JPG) ||
ExtensionMatches(Path, EXT_PNG);
}
typedef struct
{
string Path;
uint64_t *Variants;
uint64_t VariantsCount;
} variant_string;
typedef struct
{
variant_string *Variants;
uint64_t Count;
} variant_strings;
void
PushVariantUniquely(resolution_errors *E, variant_strings *VS, string Path, uint64_t Variants, config_identifier_id Identifier)
{
if(Path.Length > 0)
{
bool FoundPath = FALSE;
for(int i = 0; i < VS->Count; ++i)
{
variant_string *This = VS->Variants + i;
if(StringsMatch(This->Path, Path))
{
FoundPath = TRUE;
bool FoundVariantsMatch = FALSE;
for(int VariantsIndex = 0; VariantsIndex < This->VariantsCount; ++VariantsIndex)
{
uint64_t *Test = This->Variants + VariantsIndex;
if(*Test == Variants)
{
FoundVariantsMatch = TRUE;
}
}
if(!FoundVariantsMatch)
{
This->Variants = Fit(This->Variants, sizeof(*This->Variants), This->VariantsCount, 4, TRUE);
This->Variants[This->VariantsCount] = Variants;
++This->VariantsCount;
PushError(E, S_ERROR, 0, Identifier);
}
}
}
if(!FoundPath)
{
VS->Variants = Fit(VS->Variants, sizeof(*VS->Variants), VS->Count, 8, TRUE);
variant_string *New = VS->Variants + VS->Count;
New->Path = Path;
New->Variants = Fit(New->Variants, sizeof(*New->Variants), New->VariantsCount, 4, TRUE);
New->Variants[New->VariantsCount] = Variants;
++New->VariantsCount;
++VS->Count;
}
}
}
typedef struct
{
string String0;
string String1;
project **Projects;
uint64_t ProjectCount;
} config_string_association;
typedef struct
{
config_string_association *Associations;
config_identifier_id ID0;
config_identifier_id ID1;
uint64_t Count;
} config_string_associations;
typedef struct
{
variant_strings VariantStrings;
config_string_associations HMMLDirs;
config_string_associations BaseDirAndSearchLocation;
config_string_associations BaseDirAndPlayerLocation;
} config_verifiers;
void
PushMedium(config *C, resolution_errors *E, config_verifiers *V, project *P, scope_tree *MediumTree)
{
// TODO(matt): Do we need to warn about incomplete medium information?
medium *Medium = GetPlaceInBook(&P->Medium, P->Medium.ItemCount);
Medium->ID = ResolveString(C, E, MediumTree, &MediumTree->ID, FALSE);
for(int i = 0; i < MediumTree->PairCount; ++i)
{
config_pair *This = MediumTree->Pairs + i;
switch(This->Key)
{
case IDENT_ICON: { Medium->Icon = ResolveString(C, E, MediumTree, This, FALSE); } break;
case IDENT_ICON_NORMAL: { Medium->IconNormal = ResolveString(C, E, MediumTree, This, FALSE); } break;
case IDENT_ICON_FOCUSED: { Medium->IconFocused = ResolveString(C, E, MediumTree, This, FALSE); } break;
case IDENT_ICON_DISABLED: { Medium->IconDisabled = ResolveString(C, E, MediumTree, This, FALSE); } break;
case IDENT_NAME: { Medium->Name = ResolveString(C, E, MediumTree, This, FALSE); } break;
default: break;
}
}
for(int i = 0; i < MediumTree->IntPairCount; ++i)
{
config_int_pair *This = MediumTree->IntPairs + i;
switch(This->Key)
{
case IDENT_ICON_TYPE: { Medium->IconType = This->Value; } break;
case IDENT_ICON_VARIANTS: { Medium->IconVariants = This->Value; } break;
default: break;
}
}
for(int i = 0; i < MediumTree->BoolPairCount; ++i)
{
config_bool_pair *This = MediumTree->BoolPairs + i;
switch(This->Key)
{
case IDENT_HIDDEN: { Medium->Hidden = This->Value; } break;
default: break;
}
}
if(Medium->Icon.Length > 0)
{
if(Medium->IconType == IT_DEFAULT_UNSET)
{
if(HasImageFileExtension(Medium->Icon))
{
Medium->IconType = IT_GRAPHICAL;
}
else
{
Medium->IconType = IT_TEXTUAL;
}
}
switch(Medium->IconType)
{
case IT_GRAPHICAL:
{
PushVariantUniquely(E, &V->VariantStrings, Medium->Icon, Medium->IconVariants, IDENT_ICON);
} break;
case IT_TEXTUAL:
{
if(Medium->IconNormal.Length == 0) { Medium->IconNormal = Medium->Icon; }
if(Medium->IconFocused.Length == 0) { Medium->IconFocused = Medium->Icon; }
if(Medium->IconDisabled.Length == 0) { Medium->IconDisabled = Medium->Icon; }
} break;
default: break;
}
}
++P->Medium.ItemCount;
}
medium *
GetMediumFromProject(project *P, string ID)
{
medium *Result = 0;
for(int i = 0; i < P->Medium.ItemCount; ++i)
{
medium *This = GetPlaceInBook(&P->Medium, i);
if(StringsMatch(ID, This->ID))
{
Result = This;
break;
}
}
return Result;
}
void
PushSupport(config *C, resolution_errors *E, config_verifiers *V, person *P, scope_tree *SupportTree)
{
support *Support = GetPlaceInBook(&P->Support, P->Support.ItemCount);
Support->ID = ResolveString(C, E, SupportTree, &SupportTree->ID, FALSE);
for(int i = 0; i < SupportTree->PairCount; ++i)
{
config_pair *This = SupportTree->Pairs + i;
switch(This->Key)
{
case IDENT_ICON: { Support->Icon = StripSlashes(ResolveString(C, E, SupportTree, This, FALSE), P_REL); } break;
case IDENT_ICON_NORMAL: { Support->IconNormal = ResolveString(C, E, SupportTree, This, FALSE); } break;
case IDENT_ICON_FOCUSED: { Support->IconFocused = ResolveString(C, E, SupportTree, This, FALSE); } break;
case IDENT_URL: { Support->URL = ResolveString(C, E, SupportTree, This, FALSE); } break;
default: break;
}
}
for(int i = 0; i < SupportTree->IntPairCount; ++i)
{
config_int_pair *This = SupportTree->IntPairs + i;
switch(This->Key)
{
case IDENT_ICON_TYPE: { Support->IconType = This->Value; } break;
case IDENT_ICON_VARIANTS: { Support->IconVariants = This->Value; } break;
default: break;
}
}
if(Support->Icon.Length > 0)
{
if(Support->IconType == IT_DEFAULT_UNSET)
{
if(HasImageFileExtension(Support->Icon))
{
Support->IconType = IT_GRAPHICAL;
}
else
{
Support->IconType = IT_TEXTUAL;
}
}
switch(Support->IconType)
{
case IT_GRAPHICAL:
{
PushVariantUniquely(E, &V->VariantStrings, Support->Icon, Support->IconVariants, IDENT_ICON);
} break;
case IT_TEXTUAL:
{
if(Support->IconNormal.Length == 0) { Support->IconNormal = Support->Icon; }
if(Support->IconFocused.Length == 0) { Support->IconFocused = Support->Icon; }
} break;
default: break;
}
}
++P->Support.ItemCount;
}
void
PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *PersonTree)
{
//PrintFunctionName("PushPersonOntoConfig()");
//PrintScopeTree(PersonTree);
person *This = GetPlaceInBook(&C->Person, C->Person.ItemCount);
This->ID = ResolveString(C, E, PersonTree, &PersonTree->ID, FALSE);
for(int i = 0; i < PersonTree->PairCount; ++i)
{
if(PersonTree->Pairs[i].Key == IDENT_NAME)
{
This->Name = ResolveString(C, E, PersonTree, &PersonTree->Pairs[i], FALSE);
}
else if(PersonTree->Pairs[i].Key == IDENT_HOMEPAGE)
{
This->Homepage = ResolveString(C, E, PersonTree, &PersonTree->Pairs[i], FALSE);
}
}
InitBook(&This->Support, sizeof(support), 2, MBT_SUPPORT);
for(int i = 0; i < PersonTree->TreeCount; ++i)
{
PushSupport(C, E, V, This, &PersonTree->Trees[i]);
}
++C->Person.ItemCount;
}
void
PushPersonOntoProject(config *C, resolution_errors *E, project *P, config_pair *Actor)
{
person *Person = GetPerson(C, E, Actor);
if(Person)
{
switch(Actor->Key)
{
case IDENT_INDEXER:
{
person **Indexer = GetPlaceInBook(&P->Indexer, P->Indexer.ItemCount);
*Indexer = Person;
++P->Indexer.ItemCount;
} break;
case IDENT_COHOST:
{
person **CoHost = GetPlaceInBook(&P->CoHost, P->CoHost.ItemCount);
*CoHost = Person;
++P->CoHost.ItemCount;
} break;
case IDENT_GUEST:
{
person **Guest = GetPlaceInBook(&P->Guest, P->Guest.ItemCount);
*Guest = Person;
++P->Guest.ItemCount;
} break;
default:;
}
}
}
void PushProjectOntoProject(config *C, resolution_errors *E, config_verifiers *Verifiers, project *Parent, scope_tree *ProjectTree);
void
AddProjectToAssociation(config_string_association *A, project *P)
{
A->Projects = Fit(A->Projects, sizeof(*A->Projects), A->ProjectCount, 4, TRUE);
A->Projects[A->ProjectCount] = P;
++A->ProjectCount;
}
void
PushAssociation(config_string_associations *HMMLDirs, string *S0, string *S1, project *P)
{
HMMLDirs->Associations = Fit(HMMLDirs->Associations, sizeof(*HMMLDirs->Associations), HMMLDirs->Count, 8, TRUE);
if(S0) { HMMLDirs->Associations[HMMLDirs->Count].String0 = *S0; }
if(S1) { HMMLDirs->Associations[HMMLDirs->Count].String1 = *S1; }
AddProjectToAssociation(&HMMLDirs->Associations[HMMLDirs->Count], P);
++HMMLDirs->Count;
}
config_string_association *
GetAssociation(config_string_associations *Set, string String0, string String1)
{
config_string_association *Result = 0;
for(int i = 0; i < Set->Count; ++i)
{
config_string_association *This = Set->Associations + i;
if(StringsMatch(String0, This->String0) && StringsMatch(String1, This->String1))
{
Result = This;
break;
}
}
return Result;
}
config_string_association *
GetGlobalPathAssociation(config_string_associations *Set, string Path)
{
config_string_association *Result = 0;
for(int i = 0; i < Set->Count; ++i)
{
config_string_association *This = Set->Associations + i;
if(StringsMatchSized(Path, This->String0.Length, This->String0))
{
Path = TrimString(Path, This->String0.Length, 0);
if(Path.Length == 0 && This->String1.Length == 0)
{
Result = This;
break;
}
else if(StringsMatchSized(Path, 1, Wrap0("/")))
{
Path = TrimString(Path, 1, 0);
if(Path.Length > 0 && StringsMatchSized(Path, This->String1.Length, This->String1))
{
Result = This;
break;
}
}
}
}
return Result;
}
void
SetUniqueHMMLDir(config *C, resolution_errors *E, config_string_associations *HMMLDirs, project *P, scope_tree *ProjectTree, config_pair *HMMLDir)
{
string Result = StripSlashes(ResolveString(C, E, ProjectTree, HMMLDir, TRUE), P_ABS);
bool Clashed = FALSE;
for(int i = 0; i < HMMLDirs->Count; ++i)
{
if(StringsMatch(HMMLDirs->Associations[i].String0, Result))
{
AddProjectToAssociation(&HMMLDirs->Associations[i], P);
PushError(E, S_ERROR, &HMMLDir->Position, IDENT_HMML_DIR);
Clashed = TRUE;
break;
}
}
if(!Clashed)
{
PushAssociation(HMMLDirs, &Result, 0, P);
}
P->HMMLDir = Result;
}
void
PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, scope_tree *ProjectTree)
{
InitBook(&P->Medium, sizeof(medium), 8, MBT_MEDIUM);
InitBook(&P->Indexer, sizeof(person), 4, MBT_PERSON);
InitBook(&P->CoHost, sizeof(person), 4, MBT_PERSON);
InitBook(&P->Guest, sizeof(person), 4, MBT_PERSON);
InitBook(&P->Child, sizeof(project), 8, MBT_PROJECT);
config_string_associations *HMMLDirs = &V->HMMLDirs;
P->ID = ResolveString(C, E, ProjectTree, &ProjectTree->ID, FALSE);
if(P->ID.Length > MAX_PROJECT_ID_LENGTH)
{
ConfigErrorSizing(ProjectTree->ID.Position.Filename, ProjectTree->ID.Position.LineNumber, IDENT_PROJECT, &P->ID, MAX_PROJECT_ID_LENGTH);
PushError(E, S_ERROR, 0, IDENT_PROJECT);
}
ResetPen(&C->ResolvedVariables);
P->Lineage = DeriveLineageOfProject(C, ProjectTree);
for(int i = 0; i < ProjectTree->TreeCount; ++i)
{
if(ProjectTree->Trees[i].ID.Key == IDENT_MEDIUM)
{
PushMedium(C, E, V, P, &ProjectTree->Trees[i]);
}
}
for(int i = 0; i < ProjectTree->PairCount; ++i)
{
config_pair *This = ProjectTree->Pairs + i;
switch(This->Key)
{
case IDENT_DEFAULT_MEDIUM:
{ P->DefaultMedium = GetMediumFromProject(P, This->Value); } break;
case IDENT_HMML_DIR:
{ SetUniqueHMMLDir(C, E, HMMLDirs, P, ProjectTree, This); } break;
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;
case IDENT_PLAYER_TEMPLATE:
{ P->PlayerTemplatePath = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); } break;
case IDENT_THEME:
{ P->Theme = ResolveString(C, E, ProjectTree, This, FALSE);
if(P->Theme.Length > MAX_THEME_LENGTH)
{
ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key,
&P->Theme, MAX_THEME_LENGTH);
PushError(E, S_ERROR, 0, This->Key);
}
} break;
case IDENT_TITLE:
{
P->Title = ResolveString(C, E, ProjectTree, This, FALSE);
if(P->Title.Length > MAX_PROJECT_NAME_LENGTH)
{
ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key,
&P->Title, MAX_PROJECT_NAME_LENGTH);
PushError(E, S_ERROR, 0, This->Key);
}
} break;
case IDENT_HTML_TITLE: { P->HTMLTitle = ResolveString(C, E, ProjectTree, This, FALSE); } break;
// TODO(matt): Sort out this title list stuff
//case IDENT_TITLE_LIST_DELIMITER: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
//case IDENT_TITLE_LIST_PREFIX: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
//case IDENT_TITLE_LIST_SUFFIX: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
//case IDENT_TITLE_SUFFIX: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
case IDENT_UNIT: { P->Unit = ResolveString(C, E, ProjectTree, This, FALSE); } break;
case IDENT_BASE_DIR:
{
P->BaseDir = StripSlashes(ResolveString(C, E, ProjectTree, This, TRUE), P_ABS);
if(P->BaseDir.Length > MAX_BASE_DIR_LENGTH)
{
ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key,
&P->BaseDir, MAX_BASE_DIR_LENGTH);
PushError(E, S_ERROR, 0, This->Key);
}
} break;
case IDENT_BASE_URL:
{
P->BaseURL = ResolveString(C, E, ProjectTree, This, FALSE);
if(P->BaseURL.Length > MAX_BASE_URL_LENGTH)
{
ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key,
&P->BaseURL, MAX_BASE_URL_LENGTH);
PushError(E, S_ERROR, 0, This->Key);
}
} break;
case IDENT_PLAYER_LOCATION:
{
P->PlayerLocation = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL);
if(P->PlayerLocation.Length > MAX_RELATIVE_PAGE_LOCATION_LENGTH)
{
ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key,
&P->PlayerLocation, MAX_RELATIVE_PAGE_LOCATION_LENGTH);
PushError(E, S_ERROR, 0, This->Key);
}
} break;
case IDENT_SEARCH_LOCATION:
{
P->SearchLocation = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL);
if(P->SearchLocation.Length > MAX_RELATIVE_PAGE_LOCATION_LENGTH)
{
ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key,
&P->SearchLocation, MAX_RELATIVE_PAGE_LOCATION_LENGTH);
PushError(E, S_ERROR, 0, This->Key);
}
} break;
case IDENT_SEARCH_TEMPLATE:
{ 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;
case IDENT_STREAM_USERNAME:
{ P->StreamUsername = ResolveString(C, E, ProjectTree, This, FALSE); } break;
case IDENT_VOD_PLATFORM:
{ P->VODPlatform = ResolveString(C, E, ProjectTree, This, FALSE); } break;
case IDENT_ART: { P->Art = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); } break;
case IDENT_ICON: { P->Icon = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); } break;
case IDENT_ICON_NORMAL: { P->IconNormal = ResolveString(C, E, ProjectTree, This, FALSE); } break;
case IDENT_ICON_FOCUSED: { P->IconFocused = ResolveString(C, E, ProjectTree, This, FALSE); } break;
case IDENT_ICON_DISABLED: { P->IconDisabled = ResolveString(C, E, ProjectTree, This, FALSE); } break;
default: break;
}
}
for(int i = 0; i < ProjectTree->IntPairCount; ++i)
{
config_int_pair *This = ProjectTree->IntPairs + i;
switch(This->Key)
{
case IDENT_GENRE: { P->Genre = This->Value; } break;
case IDENT_ART_VARIANTS: { P->ArtVariants = This->Value; } break;
case IDENT_ICON_TYPE: { P->IconType = This->Value; } break;
case IDENT_ICON_VARIANTS: { P->IconVariants = This->Value; } break;
case IDENT_NUMBERING_SCHEME: { P->NumberingScheme = This->Value; } break;
default: break;
}
}
for(int i = 0; i < ProjectTree->BoolPairCount; ++i)
{
config_bool_pair *This = ProjectTree->BoolPairs + i;
switch(This->Key)
{
case IDENT_DENY_BESPOKE_TEMPLATES:
{ P->DenyBespokeTemplates = This->Value; } break;
case IDENT_IGNORE_PRIVACY:
{ P->IgnorePrivacy = This->Value; } break;
case IDENT_SINGLE_BROWSER_TAB:
{ P->SingleBrowserTab = This->Value; } break;
default: break;
}
}
for(int i = 0; i < ProjectTree->TreeCount; ++i)
{
scope_tree *This = ProjectTree->Trees + i;
if(This->ID.Key == IDENT_PROJECT)
{
PushProjectOntoProject(C, E, V, P, This);
}
}
config_string_association *BaseDirAndSearchLocation = GetAssociation(&V->BaseDirAndSearchLocation, P->BaseDir, P->SearchLocation);
if(BaseDirAndSearchLocation)
{
PushError(E, S_ERROR, 0, IDENT_SEARCH_LOCATION);
AddProjectToAssociation(BaseDirAndSearchLocation, P);
}
else
{
PushAssociation(&V->BaseDirAndSearchLocation, &P->BaseDir, &P->SearchLocation, P);
}
config_string_association *BaseDirAndPlayerLocation = GetAssociation(&V->BaseDirAndPlayerLocation, P->BaseDir, P->PlayerLocation);
if(BaseDirAndPlayerLocation)
{
PushError(E, S_ERROR, 0, IDENT_PLAYER_LOCATION);
AddProjectToAssociation(BaseDirAndPlayerLocation, P);
}
else
{
PushAssociation(&V->BaseDirAndPlayerLocation, &P->BaseDir, &P->PlayerLocation, P);
}
if(!P->DefaultMedium)
{
ResetPen(&C->ResolvedVariables);
ConfigError(0, 0, S_WARNING, "Unset default_medium for project: ", &P->Lineage);
PushError(E, S_WARNING, 0, IDENT_DEFAULT_MEDIUM);
}
if(P->Art.Length > 0)
{
PushVariantUniquely(E, &V->VariantStrings, P->Art, P->ArtVariants, IDENT_ART);
}
if(P->Icon.Length > 0)
{
if(P->IconType == IT_DEFAULT_UNSET)
{
if(HasImageFileExtension(P->Icon))
{
P->IconType = IT_GRAPHICAL;
}
else
{
P->IconType = IT_TEXTUAL;
}
}
switch(P->IconType)
{
case IT_GRAPHICAL:
{
PushVariantUniquely(E, &V->VariantStrings, P->Icon, P->IconVariants, IDENT_ICON);
} break;
case IT_TEXTUAL:
{
if(P->IconNormal.Length == 0) { P->IconNormal = P->Icon; }
if(P->IconFocused.Length == 0) { P->IconFocused = P->Icon; }
if(P->IconDisabled.Length == 0) { P->IconDisabled = P->Icon; }
} break;
default: break;
}
}
C->RespectingPrivacy |= !P->IgnorePrivacy;
}
void
PushProjectOntoProject(config *C, resolution_errors *E, config_verifiers *Verifiers, project *Parent, scope_tree *ProjectTree)
{
project *P = GetPlaceInBook(&Parent->Child, Parent->Child.ItemCount);
PushProject(C, E, Verifiers, P, ProjectTree);
P->Parent = Parent;
++Parent->Child.ItemCount;
}
project *
GetProjectFromProject(project *Parent, string ID)
{
project *Result = 0;
if(Parent)
{
for(int i = 0; i < Parent->Child.ItemCount; ++i)
{
project *This = GetPlaceInBook(&Parent->Child, i);
if(StringsMatch(This->ID, ID))
{
Result = This;
break;
}
}
}
return Result;
}
void
PushProjectOntoConfig(config *C, resolution_errors *E, config_verifiers *Verifiers, scope_tree *ProjectTree)
{
project *P = GetPlaceInBook(&C->Project, C->Project.ItemCount);
PushProject(C, E, Verifiers, P, ProjectTree);
++C->Project.ItemCount;
}
project *
GetProjectFromConfig(config *C, string ID)
{
project *Result = 0;
if(C)
{
for(int i = 0; i < C->Project.ItemCount; ++i)
{
project *This = GetPlaceInBook(&C->Project, i);
if(StringsMatch(This->ID, ID))
{
Result = This;
break;
}
}
}
return Result;
}
void
PrintAssociationClashes(config_string_associations *A)
{
for(int i = 0; i < A->Count; ++i)
{
config_string_association *Association = A->Associations + i;
if(Association->ProjectCount > 1)
{
ConfigError(0, 0, S_ERROR, "Multiple projects share the same:", 0);
config_pair Assoc0 = { .Key = A->ID0, .Value = Association->String0 };
int IndentLevel = 2;
PrintPair(&Assoc0, ": ", FALSE, IndentLevel, FALSE, FALSE);
if(A->ID1 != IDENT_NULL)
{
config_pair Assoc1 = { .Key = A->ID1, .Value = Association->String1 };
PrintPair(&Assoc1, ": ", FALSE, IndentLevel, TRUE, FALSE);
}
fprintf(stderr, "\n");
for(int j = 0; j < Association->ProjectCount; ++j)
{
project *P = Association->Projects[j];
fprintf(stderr, " ");
if(P)
{
PrintStringC(CS_CYAN, P->Lineage);
}
else
{
PrintStringC(CS_RED, Wrap0("[global scope: "));
// TODO(matt): Maybe generalise this, next time we're in this situation
if(A->ID0 == IDENT_BASE_DIR && A->ID1 == IDENT_SEARCH_LOCATION)
{
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_GLOBAL_SEARCH_DIR].String);
}
PrintStringC(CS_RED, Wrap0("]"));
}
fprintf(stderr, "\n");
}
}
}
}
void
PrintVariants(uint64_t V, colour_code ColourCode, string Margin)
{
int TermCols = GetTerminalColumns();
int AvailableColumnsForValue = TermCols - Margin.Length;
int RunningAvailableColumns = TermCols - Margin.Length;
PrintString(Margin);
bool NeedsSpacing = FALSE;
if(V)
{
for(int VariantShifterIndex = 0; VariantShifterIndex < AVS_COUNT; ++VariantShifterIndex)
{
if(V & (1 << VariantShifterIndex))
{
char *String = ArtVariantStrings[VariantShifterIndex];
int Length = StringLength(String);
if(RunningAvailableColumns - Length >= 0)
{
fprintf(stderr, "%s", NeedsSpacing ? " " : "");
}
else
{
fprintf(stderr, "\n");
PrintString(Margin);
NeedsSpacing = FALSE;
RunningAvailableColumns = AvailableColumnsForValue;
}
PrintC(ColourCode, String);
RunningAvailableColumns -= Length + NeedsSpacing;
NeedsSpacing = TRUE;
}
}
}
else
{
PrintC(CS_YELLOW, "[unset]");
}
}
void
PrintVariantInconsistencies(variant_strings *V)
{
for(int i = 0; i < V->Count; ++i)
{
variant_string *This = V->Variants + i;
if(This->VariantsCount > 1)
{
ConfigError(0, 0, S_ERROR, "Sprite has inconsistently set variants ", &This->Path);
for(int j = 0; j < This->VariantsCount; ++j)
{
string Margin = Wrap0(" ");
uint64_t V = This->Variants[j];
PrintVariants(V, j % 2 ? CS_CYAN_BOLD : CS_YELLOW_BOLD, Margin);
fprintf(stderr, "\n");
}
}
}
}
void
FreeAssociations(config_string_associations *A)
{
for(int i = 0; i < A->Count; ++i)
{
FreeAndResetCount(A->Associations[i].Projects, A->Associations[i].ProjectCount);
}
FreeAndResetCount(A->Associations, A->Count);
}
void
FreeVariants(variant_strings *V)
{
for(int i = 0; i < V->Count; ++i)
{
FreeAndResetCount(V->Variants[i].Variants, V->Variants[i].VariantsCount);
}
FreeAndResetCount(V->Variants, V->Count);
}
void
FreeVerifiers(config_verifiers *Verifiers)
{
FreeAssociations(&Verifiers->BaseDirAndSearchLocation);
FreeAssociations(&Verifiers->BaseDirAndPlayerLocation);
FreeAssociations(&Verifiers->HMMLDirs);
FreeVariants(&Verifiers->VariantStrings);
}
config *
ResolveVariables(scope_tree *S)
{
Assert(LOG_COUNT == ArrayCount(LogLevelStrings));
config *Result = calloc(1, sizeof(config));
InitBook(&Result->ResolvedVariables, 1, Kilobytes(1), MBT_STRING);
InitBook(&Result->Person, sizeof(person), 8, MBT_PERSON);
InitBook(&Result->Project, sizeof(project), 8, MBT_PROJECT);
resolution_errors Errors = {};
config_verifiers Verifiers = {};
Verifiers.HMMLDirs.ID0 = IDENT_HMML_DIR;
Verifiers.BaseDirAndSearchLocation.ID0 = IDENT_BASE_DIR;
Verifiers.BaseDirAndSearchLocation.ID1 = IDENT_SEARCH_LOCATION;
Verifiers.BaseDirAndPlayerLocation.ID0 = IDENT_BASE_DIR;
Verifiers.BaseDirAndPlayerLocation.ID1 = IDENT_PLAYER_LOCATION;
for(int i = 0; i < S->PairCount; ++i)
{
switch(S->Pairs[i].Key)
{
case IDENT_DB_LOCATION:
{ Result->DatabaseLocation = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], TRUE), P_ABS); } break;
case IDENT_GLOBAL_SEARCH_DIR:
{
Result->GlobalSearchDir = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], TRUE), P_ABS);
if(Result->GlobalSearchDir.Length > MAX_BASE_DIR_LENGTH)
{
ConfigErrorSizing(S->Pairs[i].Position.Filename, S->Pairs[i].Position.LineNumber, S->Pairs[i].Key,
&Result->GlobalSearchDir, MAX_BASE_DIR_LENGTH);
PushError(&Errors, S_ERROR, 0, S->Pairs[i].Key);
}
} break;
case IDENT_GLOBAL_SEARCH_URL:
{
Result->GlobalSearchURL = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], TRUE), P_ABS);
if(Result->GlobalSearchURL.Length > MAX_BASE_URL_LENGTH)
{
ConfigErrorSizing(S->Pairs[i].Position.Filename, S->Pairs[i].Position.LineNumber, S->Pairs[i].Key,
&Result->GlobalSearchURL, MAX_BASE_URL_LENGTH);
PushError(&Errors, S_ERROR, 0, S->Pairs[i].Key);
}
} break;
case IDENT_GLOBAL_TEMPLATES_DIR:
{ Result->GlobalTemplatesDir = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], TRUE), P_ABS); } break;
case IDENT_GLOBAL_SEARCH_TEMPLATE:
{ Result->GlobalSearchTemplatePath = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], FALSE), P_REL); } break;
case IDENT_GLOBAL_THEME:
{
Result->GlobalTheme = ResolveString(Result, &Errors, S, &S->Pairs[i], FALSE);
if(Result->GlobalTheme.Length > MAX_THEME_LENGTH)
{
ConfigErrorSizing(S->Pairs[i].Position.Filename, S->Pairs[i].Position.LineNumber, S->Pairs[i].Key,
&Result->GlobalTheme, MAX_THEME_LENGTH);
PushError(&Errors, S_ERROR, 0, S->Pairs[i].Key);
}
} break;
case IDENT_ASSETS_ROOT_DIR:
{
Result->AssetsRootDir = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], TRUE), P_ABS);
if(Result->AssetsRootDir.Length > MAX_ROOT_DIR_LENGTH)
{
ConfigErrorSizing(S->Pairs[i].Position.Filename, S->Pairs[i].Position.LineNumber, S->Pairs[i].Key,
&Result->AssetsRootDir, MAX_ROOT_DIR_LENGTH);
PushError(&Errors, S_ERROR, 0, S->Pairs[i].Key);
}
} break;
case IDENT_ASSETS_ROOT_URL:
{
Result->AssetsRootURL = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], TRUE), P_ABS);
if(Result->AssetsRootURL.Length > MAX_ROOT_URL_LENGTH)
{
ConfigErrorSizing(S->Pairs[i].Position.Filename, S->Pairs[i].Position.LineNumber, S->Pairs[i].Key,
&Result->AssetsRootURL, MAX_ROOT_URL_LENGTH);
PushError(&Errors, S_ERROR, 0, S->Pairs[i].Key);
}
} break;
case IDENT_CSS_PATH:
{
Result->CSSDir = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], FALSE), P_REL);
if(Result->CSSDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH)
{
ConfigErrorSizing(S->Pairs[i].Position.Filename, S->Pairs[i].Position.LineNumber, S->Pairs[i].Key,
&Result->CSSDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH);
PushError(&Errors, S_ERROR, 0, S->Pairs[i].Key);
}
} break;
case IDENT_IMAGES_PATH:
{
Result->ImagesDir = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], FALSE), P_REL);
if(Result->ImagesDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH)
{
ConfigErrorSizing(S->Pairs[i].Position.Filename, S->Pairs[i].Position.LineNumber, S->Pairs[i].Key,
&Result->ImagesDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH);
PushError(&Errors, S_ERROR, 0, S->Pairs[i].Key);
}
} break;
case IDENT_JS_PATH:
{
Result->JSDir = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], FALSE), P_REL);
if(Result->JSDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH)
{
ConfigErrorSizing(S->Pairs[i].Position.Filename, S->Pairs[i].Position.LineNumber, S->Pairs[i].Key,
&Result->JSDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH);
PushError(&Errors, S_ERROR, 0, S->Pairs[i].Key);
}
} break;
case IDENT_QUERY_STRING:
{ Result->QueryString = ResolveString(Result, &Errors, S, &S->Pairs[i], FALSE); } break;
case IDENT_CACHE_DIR:
{ Result->CacheDir = StripSlashes(ResolveString(Result, &Errors, S, &S->Pairs[i], TRUE), P_ABS); } break;
default: break;
}
}
int SecondsPerMinute = 60;
for(int i = 0; i < S->IntPairCount; ++i)
{
switch(S->IntPairs[i].Key)
{
case IDENT_PRIVACY_CHECK_INTERVAL:
{ Result->PrivacyCheckInterval = SecondsPerMinute * S->IntPairs[i].Value; } break;
case IDENT_LOG_LEVEL:
{ Result->LogLevel = S->IntPairs[i].Value; } break;
default: break;
}
}
for(int i = 0; i < S->TreeCount; ++i)
{
if(S->Trees[i].ID.Key == IDENT_PERSON)
{
PushPersonOntoConfig(Result, &Errors, &Verifiers, &S->Trees[i]);
}
}
for(int i = 0; i < S->TreeCount; ++i)
{
if(S->Trees[i].ID.Key == IDENT_PROJECT)
{
PushProjectOntoConfig(Result, &Errors, &Verifiers, &S->Trees[i]);
}
}
config_string_association *BaseDirAndSearchLocation = GetGlobalPathAssociation(&Verifiers.BaseDirAndSearchLocation, Result->GlobalSearchDir);
if(BaseDirAndSearchLocation)
{
PushError(&Errors, S_ERROR, 0, IDENT_GLOBAL_SEARCH_DIR);
AddProjectToAssociation(BaseDirAndSearchLocation, 0);
}
// TODO(matt): Mandate that, if a GlobalSearchDir or GlobalSearchURL is set, then both must be set
if(!Result->GlobalTheme.Length)
{
ConfigErrorUnset(IDENT_GLOBAL_THEME);
PushError(&Errors, S_ERROR, 0, IDENT_GLOBAL_THEME);
}
if(Errors.ErrorCount > 0)
{
PrintAssociationClashes(&Verifiers.HMMLDirs);
PrintAssociationClashes(&Verifiers.BaseDirAndSearchLocation);
PrintAssociationClashes(&Verifiers.BaseDirAndPlayerLocation);
PrintVariantInconsistencies(&Verifiers.VariantStrings);
Free(Result);
}
FreeVerifiers(&Verifiers);
FreeErrors(&Errors);
return Result;
}
void
PrintLogLevel(char *Title, log_level Level, char *Delimiter, uint64_t Indentation, bool AppendNewline)
{
Indent(Indentation);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_LOG_LEVEL].String);
fprintf(stderr, "%s", Delimiter);
PrintC(CS_GREEN_BOLD, LogLevelStrings[Level]);
if(AppendNewline) { fprintf(stderr, "\n"); }
}
typedef struct
{
char *UpperLeftCorner;
char *UpperLeft;
char *Horizontal;
char *UpperRight;
char *Vertical;
char *LowerLeftCorner;
char *LowerLeft;
char *Margin;
char *Delimiter;
char *Separator;
} typography;
void
PrintVerticals(typography *T, int Count)
{
for(int i = 0; i < Count; ++i)
{
fprintf(stderr, "%s", T->Vertical);
}
}
void
CarriageReturn(typography *T, int VerticalsRequired)
{
fprintf(stderr, "\n");
PrintVerticals(T, VerticalsRequired);
}
uint64_t
TypesetIconType(typography *T, uint8_t Generation, icon_type Type, bool PrependNewline, bool AppendNewline)
{
uint64_t Result = 0;
// TODO(matt): Implement me, please!
//CarriageReturn(T, Generation);
//fprintf(stderr, "%s", T.Margin);
#if 0
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_ICON_TYPE].String);
fprintf(stderr, "%s", T.Delimiter);
PrintC(CS_GREEN_BOLD, IconTypeStrings[Type]);
fprintf(stderr, "\n");
#endif
if(PrependNewline)
{
CarriageReturn(T, Generation);
++Result;
}
fprintf(stderr, "%s", T->Margin);
config_identifier_id Key = IDENT_ICON_TYPE;
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[Key].String);
fprintf(stderr, "%s", T->Delimiter);
if(Type == IT_DEFAULT_UNSET)
{
PrintC(CS_YELLOW, "[unset]");
}
else
{
PrintStringC(CS_GREEN_BOLD, Wrap0(IconTypeStrings[Type]));
}
if(AppendNewline)
{
CarriageReturn(T, Generation);
++Result;
}
return Result;
}
void
TypesetVariants(typography *T, uint8_t Generation, config_identifier_id Key, uint64_t Value, int AvailableColumns, uint8_t *RowsRequired, bool PrependNewline, bool AppendNewline)
{
// NOTE(matt): RowsRequired is a value passed only by PrintSupport().
if(PrependNewline)
{
CarriageReturn(T, Generation);
if(RowsRequired) { --*RowsRequired; }
}
if(RowsRequired)
{
fprintf(stderr, "%s%s ", *RowsRequired == 1 ? T->LowerLeft : T->Vertical, T->Margin);
}
fprintf(stderr, "%s", T->Margin);
int CharactersInKeyPlusDelimiter = StringLength(ConfigIdentifiers[Key].String) + StringLength(T->Delimiter);
int AvailableColumnsForValue = AvailableColumns - CharactersInKeyPlusDelimiter;
int RunningAvailableColumns = AvailableColumnsForValue;
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[Key].String);
fprintf(stderr, "%s", T->Delimiter);
bool NeedsSpacing = FALSE;
if(Value)
{
for(int VariantShifterIndex = 0; VariantShifterIndex < AVS_COUNT; ++VariantShifterIndex)
{
if(Value & (1 << VariantShifterIndex))
{
char *String = ArtVariantStrings[VariantShifterIndex];
int Length = StringLength(String);
if(RunningAvailableColumns - Length >= 0)
{
fprintf(stderr, "%s", NeedsSpacing ? " " : "");
}
else
{
CarriageReturn(T, Generation);
if(RowsRequired)
{
--*RowsRequired;
fprintf(stderr, "%s%s ", *RowsRequired == 1 ? T->LowerLeft : T->Vertical, T->Margin);
}
fprintf(stderr, "%s", T->Margin);
for(int i = 0; i < CharactersInKeyPlusDelimiter; ++i)
{
fprintf(stderr, "%s", T->Margin);
}
NeedsSpacing = FALSE;
RunningAvailableColumns = AvailableColumnsForValue;
}
PrintC(CS_GREEN_BOLD, String);
RunningAvailableColumns -= Length + NeedsSpacing;
NeedsSpacing = TRUE;
}
}
}
else
{
PrintC(CS_YELLOW, "[unset]");
}
if(AppendNewline)
{
CarriageReturn(T, Generation);
}
}
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)
{
PrintC(CS_BLACK_BOLD, " [hidden]");
}
fprintf(stderr, "%s", Delimiter);
PrintStringC(CS_MAGENTA, M->Name);
fprintf(stderr, " (");
if(M->Icon.Length > 0)
{
PrintStringI(M->Icon, Indentation);
}
if(M->IconNormal.Length > 0)
{
PrintStringI(M->IconNormal, Indentation);
}
if(M->IconFocused.Length > 0)
{
PrintStringI(M->IconFocused, Indentation);
}
TypesetIconType(T, Indentation, M->IconType, FALSE, AppendNewline);
TypesetVariants(T, Indentation, IDENT_ICON_VARIANTS, M->IconVariants, 0, 0, FALSE, AppendNewline);
fprintf(stderr, ")");
if(AppendNewline)
{
fprintf(stderr, "\n");
}
}
void
PrintSupport(support *S, typography *Typography, int IndentationLevel, uint8_t *RowsRequired)
{
bool ShouldFillSyntax = FALSE;
config_pair ID = { .Key = IDENT_SUPPORT, .Value = S->ID };
fprintf(stderr, "%s%s", *RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical, Typography->Margin);
PrintPair(&ID, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--*RowsRequired;
if(S->URL.Length > 0)
{
config_pair URL = { .Key = IDENT_URL, .Value = S->URL };
fprintf(stderr, "%s%s ", *RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical, Typography->Margin);
PrintPair(&URL, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--*RowsRequired;
}
if(S->Icon.Length > 0)
{
config_pair Icon = { .Key = IDENT_ICON, .Value = S->Icon };
fprintf(stderr, "%s%s ", *RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical, Typography->Margin);
PrintPair(&Icon, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--*RowsRequired;
}
if(S->IconNormal.Length > 0)
{
config_pair IconNormal = { .Key = IDENT_ICON_NORMAL, .Value = S->IconNormal };
fprintf(stderr, "%s%s ", *RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical, Typography->Margin);
PrintPair(&IconNormal, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--*RowsRequired;
}
if(S->IconFocused.Length > 0)
{
config_pair IconFocused = { .Key = IDENT_ICON_FOCUSED, .Value = S->IconFocused };
fprintf(stderr, "%s%s ", *RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical, Typography->Margin);
PrintPair(&IconFocused, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--*RowsRequired;
}
#if 1
// TODO(matt): FIX ME
fprintf(stderr, "%s%s ", *RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical, Typography->Margin);
*RowsRequired -= TypesetIconType(Typography, IndentationLevel, S->IconType, FALSE, TRUE);
uint8_t StartingColumn = 4;
uint64_t AvailableColumns = GetTerminalColumns() - StartingColumn;
TypesetVariants(Typography, IndentationLevel, IDENT_ICON_VARIANTS, S->IconVariants, AvailableColumns, RowsRequired, FALSE, TRUE);
#endif
}
uint8_t
GetRowsRequiredForVariants(typography *T, config_identifier_id Key, uint64_t Value, int AvailableColumns)
{
int CharactersInKeyPlusDelimiter = StringLength(ConfigIdentifiers[Key].String) + StringLength(T->Delimiter);
int AvailableColumnsForValue = AvailableColumns - CharactersInKeyPlusDelimiter;
int RunningAvailableColumns = AvailableColumnsForValue;
uint8_t Result = 1;
bool NeedsSpacing = FALSE;
for(int VariantShifterIndex = 0; VariantShifterIndex < AVS_COUNT; ++VariantShifterIndex)
{
if(Value & (1 << VariantShifterIndex))
{
char *String = ArtVariantStrings[VariantShifterIndex];
int Length = StringLength(String);
if(!(RunningAvailableColumns - Length >= 0))
{
++Result;
NeedsSpacing = FALSE;
RunningAvailableColumns = AvailableColumnsForValue;
}
RunningAvailableColumns -= Length + NeedsSpacing;
NeedsSpacing = TRUE;
}
}
return Result;
}
uint8_t
GetRowsRequiredForPersonInfo(typography *T, person *P)
{
uint8_t RowsRequired = 0;
if(P->Name.Length > 0) { ++RowsRequired; }
if(P->Homepage.Length > 0) { ++RowsRequired; }
for(int i = 0; i < P->Support.ItemCount; ++i)
{
++RowsRequired;
support *This = GetPlaceInBook(&P->Support, i);
if(This->URL.Length > 0) { ++RowsRequired; }
if(This->Icon.Length > 0) { ++RowsRequired; }
if(This->IconNormal.Length > 0) { ++RowsRequired; }
if(This->IconFocused.Length > 0) { ++RowsRequired; }
++RowsRequired; // NOTE(matt): For TypesetIconType()
int StartingColumn = 4;
RowsRequired += GetRowsRequiredForVariants(T, IDENT_ICON_VARIANTS, This->IconVariants, GetTerminalColumns() - StartingColumn);
}
return RowsRequired;
}
void
PrintPerson(person *P, config_identifier_id Role, typography *Typography)
{
bool HaveInfo = P->Name.Length > 0 || P->Homepage.Length > 0 || P->Support.ItemCount > 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, P->ID);
fprintf(stderr, "\n");
uint8_t RowsRequired = GetRowsRequiredForPersonInfo(Typography, P);
int IndentationLevel = 0;
bool ShouldFillSyntax = FALSE;
if(P->Name.Length > 0)
{
fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair Name = { .Key = IDENT_NAME, .Value = P->Name }; PrintPair(&Name, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
if(P->Homepage.Length > 0)
{
fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair Homepage = { .Key = IDENT_HOMEPAGE, .Value = P->Homepage }; PrintPair(&Homepage, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
for(int i = 0; i < P->Support.ItemCount; ++i)
{
support *This = GetPlaceInBook(&P->Support, i);
PrintSupport(This, Typography, IndentationLevel, &RowsRequired);
}
}
void
PrintLineage(string Lineage, bool AppendNewline)
{
string Parent = StripComponentFromPath(Lineage);
PrintStringC(CS_BLACK_BOLD, Parent);
if(Parent.Length > 0)
{
PrintC(CS_BLACK_BOLD, "/");
}
string Us = GetFinalComponent(Lineage);
PrintStringC(CS_BLUE_BOLD, Us);
if(AppendNewline) { fprintf(stderr, "\n"); }
}
uint8_t
GetColumnsRequiredForMedium(medium *M, typography *T)
{
uint8_t Result = M->ID.Length + StringLength(T->Delimiter) + M->Name.Length + StringLength(" (") + M->Icon.Length + StringLength(")");
if(M->Hidden) { Result += StringLength(" [hidden]"); }
return Result;
}
void
PrintTitle(char *Text, int AvailableColumns)
{
char *LeftmostChar = "";
char *InnerChar = "";
char *RightmostChar = "";
int LeftmostCharLength = 1;
int SpacesRequired = 2;
int RightmostCharLength = 1;
int InnerCharsRequired = AvailableColumns - LeftmostCharLength - StringLength(Text) - RightmostCharLength - SpacesRequired;
int LeftCharsRequired = 4;
int RightCharsRequired = InnerCharsRequired - LeftCharsRequired;
fprintf(stderr, "%s", LeftmostChar);
for(int i = 0; i < LeftCharsRequired; ++i)
{
fprintf(stderr, "%s", InnerChar);
}
fprintf(stderr, " %s ", Text);
for(int i = 0; i < RightCharsRequired; ++i)
{
fprintf(stderr, "%s", InnerChar);
}
fprintf(stderr, "%s", RightmostChar);
}
void
PrintMedia(project *P, typography *T, uint8_t Generation, int AvailableColumns)
{
int IndentationLevel = 0;
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
PrintTitle("Media", AvailableColumns);
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
//AvailableColumns -= Generation + 1;
int RunningAvailableColumns = AvailableColumns;
medium *This = GetPlaceInBook(&P->Medium, 0);
int ColumnsRequired = GetColumnsRequiredForMedium(This, T);
if(ColumnsRequired <= RunningAvailableColumns)
{
PrintMedium(T, This, T->Delimiter, IndentationLevel, FALSE);
}
RunningAvailableColumns -= ColumnsRequired;
int StringLengthOfSpacePlusSeparator = 2;
for(int i = 1; i < P->Medium.ItemCount; ++i)
{
This = GetPlaceInBook(&P->Medium, i);
ColumnsRequired = GetColumnsRequiredForMedium(This, T);
if(RunningAvailableColumns >= StringLengthOfSpacePlusSeparator)
{
fprintf(stderr, " %s", T->Separator);
RunningAvailableColumns -= StringLengthOfSpacePlusSeparator;
if(ColumnsRequired + 1 <= RunningAvailableColumns)
{
fprintf(stderr, " ");
--RunningAvailableColumns;
PrintMedium(T, This, T->Delimiter, IndentationLevel, FALSE);
}
else
{
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
RunningAvailableColumns = AvailableColumns;
PrintMedium(T, This, T->Delimiter, IndentationLevel, FALSE);
}
}
else
{
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
RunningAvailableColumns = AvailableColumns;
fprintf(stderr, "%s ", T->Separator);
RunningAvailableColumns -= StringLengthOfSpacePlusSeparator;
PrintMedium(T, This, T->Delimiter, IndentationLevel, FALSE);
}
RunningAvailableColumns -= ColumnsRequired;
}
}
void
TypesetPair(typography *T, uint8_t Generation, config_identifier_id Key, string Value, int AvailableColumns)
{
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
int CharactersInKeyPlusDelimiter = StringLength(ConfigIdentifiers[Key].String) + StringLength(T->Delimiter);
int AvailableColumnsForValue = AvailableColumns - CharactersInKeyPlusDelimiter;
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[Key].String);
fprintf(stderr, "%s", T->Delimiter);
int CharactersToWrite = Value.Length;
if(Value.Length > 0)
{
string Pen = { .Base = Value.Base + Value.Length - CharactersToWrite,
.Length = MIN(Value.Length, AvailableColumnsForValue) };
PrintStringC(CS_GREEN_BOLD, Pen);
CharactersToWrite -= Pen.Length;
while(CharactersToWrite > 0)
{
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
for(int i = 0; i < CharactersInKeyPlusDelimiter; ++i)
{
fprintf(stderr, "%s", T->Margin);
}
string Pen = { .Base = Value.Base + Value.Length - CharactersToWrite,
.Length = MIN(CharactersToWrite, AvailableColumnsForValue) };
PrintStringC(CS_GREEN_BOLD, Pen);
CharactersToWrite -= Pen.Length;
}
}
else
{
PrintC(CS_YELLOW, "[unset]");
}
}
void
TypesetBool(typography *T, uint8_t Generation, config_identifier_id Key, bool Value)
{
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
config_bool_pair Pair = { .Key = Key, .Value = Value };
bool ShouldFillSyntax = FALSE;
PrintBoolPair(&Pair, T->Delimiter, ShouldFillSyntax, 0, FALSE, FALSE);
}
void
TypesetGenre(typography *T, uint8_t Generation, genre G)
{
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_GENRE].String);
fprintf(stderr, "%s", T->Delimiter);
PrintC(CS_GREEN_BOLD, GenreStrings[G]);
}
void
TypesetNumberingScheme(typography *T, uint8_t Generation, numbering_scheme N)
{
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_NUMBERING_SCHEME].String);
fprintf(stderr, "%s", T-> Delimiter);
PrintC(CS_GREEN_BOLD, NumberingSchemeStrings[N]);
}
void
PrintProject(project *P, typography *T, int Ancestors, int IndentationLevel, int TerminalColumns)
{
int Generation = Ancestors + 1;
CarriageReturn(T, Ancestors);
fprintf(stderr, "%s%s%s%s%s ", T->UpperLeftCorner,
T->Horizontal, T->Horizontal, T->Horizontal, T->UpperRight);
PrintLineage(P->Lineage, FALSE);
int CharactersInMargin = 1;
int AvailableColumns = TerminalColumns - (Generation + CharactersInMargin);
if(P->Medium.ItemCount > 0)
{
PrintMedia(P, T, Generation, AvailableColumns);
}
CarriageReturn(T, Generation);
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
PrintTitle("Settings", AvailableColumns);
TypesetPair(T, Generation, IDENT_TITLE, P->Title, AvailableColumns);
TypesetPair(T, Generation, IDENT_HTML_TITLE, P->HTMLTitle, AvailableColumns);
TypesetPair(T, Generation, IDENT_DEFAULT_MEDIUM, P->DefaultMedium ? P->DefaultMedium->ID : EmptyString(), AvailableColumns);
TypesetPair(T, Generation, IDENT_UNIT, P->Unit, AvailableColumns);
TypesetPair(T, Generation, IDENT_HMML_DIR, P->HMMLDir, AvailableColumns);
TypesetPair(T, Generation, IDENT_TEMPLATES_DIR, P->TemplatesDir, AvailableColumns);
TypesetPair(T, Generation, IDENT_SEARCH_TEMPLATE, P->SearchTemplatePath, AvailableColumns);
TypesetPair(T, Generation, IDENT_PLAYER_TEMPLATE, P->PlayerTemplatePath, AvailableColumns);
CarriageReturn(T, Generation);
TypesetPair(T, Generation, IDENT_BASE_DIR, P->BaseDir, AvailableColumns);
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);
TypesetPair(T, Generation, IDENT_STREAM_USERNAME, P->StreamUsername, AvailableColumns);
TypesetPair(T, Generation, IDENT_VOD_PLATFORM, P->VODPlatform, AvailableColumns);
TypesetPair(T, Generation, IDENT_THEME, P->Theme, AvailableColumns);
CarriageReturn(T, Generation);
TypesetPair(T, Generation, IDENT_ART, P->Art, AvailableColumns);
TypesetVariants(T, Generation, IDENT_ART_VARIANTS, P->ArtVariants, AvailableColumns, 0, TRUE, FALSE);
TypesetPair(T, Generation, IDENT_ICON, P->Icon, AvailableColumns);
TypesetPair(T, Generation, IDENT_ICON_NORMAL, P->IconNormal, AvailableColumns);
TypesetPair(T, Generation, IDENT_ICON_FOCUSED, P->IconFocused, AvailableColumns);
TypesetPair(T, Generation, IDENT_ICON_DISABLED, P->IconDisabled, AvailableColumns);
TypesetIconType(T, Generation, P->IconType, TRUE, FALSE);
TypesetVariants(T, Generation, IDENT_ICON_VARIANTS, P->IconVariants, AvailableColumns, 0, TRUE, FALSE);
CarriageReturn(T, Generation);
TypesetGenre(T, Generation, P->Genre);
TypesetNumberingScheme(T, Generation, P->NumberingScheme);
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);
}
for(int i = 0; i < P->Indexer.ItemCount; ++i)
{
person **Indexer = GetPlaceInBook(&P->Indexer, i);
TypesetPair(T, Generation, IDENT_INDEXER, (*Indexer)->ID, AvailableColumns);
}
for(int i = 0; i < P->CoHost.ItemCount; ++i)
{
person **CoHost = GetPlaceInBook(&P->CoHost, i);
TypesetPair(T, Generation, IDENT_COHOST, (*CoHost)->ID, AvailableColumns);
}
for(int i = 0; i < P->Guest.ItemCount; ++i)
{
person **Guest = GetPlaceInBook(&P->Guest, i);
TypesetPair(T, Generation, IDENT_GUEST, (*Guest)->ID, AvailableColumns);
}
if(P->Child.ItemCount)
{
CarriageReturn(T, Generation);
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
PrintTitle("Children", AvailableColumns);
++Ancestors;
for(int i = 0; i < P->Child.ItemCount; ++i)
{
project *This = GetPlaceInBook(&P->Child, i);
PrintProject(This, T, Ancestors, IndentationLevel, TerminalColumns);
}
--Ancestors;
}
CarriageReturn(T, Ancestors);
fprintf(stderr, "%s%s%s%s%s ", T->LowerLeftCorner,
T->Horizontal, T->Horizontal, T->Horizontal, T->UpperRight);
PrintLineage(P->Lineage, FALSE);
}
void
PrintConfig(config *C, bool ShouldClearTerminal)
{
if(C)
{
int TermCols = GetTerminalColumns();
if(ShouldClearTerminal) { ClearTerminal(); }
typography Typography =
{
.UpperLeftCorner = "",
.UpperLeft = "",
.Horizontal = "",
.UpperRight = "",
.Vertical = "",
.LowerLeftCorner = "",
.LowerLeft = "",
.Margin = " ",
.Delimiter = ": ",
.Separator = "",
};
// separate
fprintf(stderr, "\n");
PrintTitle("Global Settings", TermCols);
int IndentationLevel = 0;
int AvailableColumns = TermCols - StringLength(Typography.Margin);
TypesetPair(&Typography, 0, IDENT_DB_LOCATION, C->DatabaseLocation, AvailableColumns);
TypesetPair(&Typography, 0, IDENT_CACHE_DIR, C->CacheDir, AvailableColumns);
fprintf(stderr, "\n");
TypesetPair(&Typography, 0, IDENT_GLOBAL_TEMPLATES_DIR, C->GlobalTemplatesDir, AvailableColumns);
TypesetPair(&Typography, 0, IDENT_GLOBAL_SEARCH_TEMPLATE, C->GlobalSearchTemplatePath, AvailableColumns);
TypesetPair(&Typography, 0, IDENT_GLOBAL_SEARCH_DIR, C->GlobalSearchDir, AvailableColumns);
TypesetPair(&Typography, 0, IDENT_GLOBAL_SEARCH_URL, C->GlobalSearchURL, AvailableColumns);
TypesetPair(&Typography, 0, IDENT_GLOBAL_THEME, C->GlobalTheme, AvailableColumns);
fprintf(stderr, "\n");
TypesetPair(&Typography, 0, IDENT_ASSETS_ROOT_DIR, C->AssetsRootDir, AvailableColumns);
TypesetPair(&Typography, 0, IDENT_ASSETS_ROOT_URL, C->AssetsRootURL, AvailableColumns);
TypesetPair(&Typography, 0, IDENT_CSS_PATH, C->CSSDir, AvailableColumns);
TypesetPair(&Typography, 0, IDENT_IMAGES_PATH, C->ImagesDir, AvailableColumns);
TypesetPair(&Typography, 0, IDENT_JS_PATH, C->JSDir, AvailableColumns);
TypesetPair(&Typography, 0, IDENT_QUERY_STRING, C->QueryString, AvailableColumns);
fprintf(stderr, "\n"
"\n"
"%s", Typography.Margin);
bool ShouldFillSyntax = FALSE;
PrintBool("Respecting Privacy (derived from projects)", C->RespectingPrivacy, Typography.Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
fprintf(stderr, "%s", Typography.Margin);
int SecondsPerMinute = 60;
config_int_pair PrivacyCheckInterval = { .Key = IDENT_PRIVACY_CHECK_INTERVAL, .Value = C->PrivacyCheckInterval / SecondsPerMinute }; PrintIntPair(&PrivacyCheckInterval, Typography.Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
fprintf(stderr, "%s", Typography.Margin);
PrintLogLevel(ConfigIdentifiers[IDENT_LOG_LEVEL].String, C->LogLevel, Typography.Delimiter, IndentationLevel, TRUE);
fprintf(stderr, "\n");
PrintTitle("People", TermCols);
for(int i = 0; i < C->Person.ItemCount; ++i)
{
person *This = GetPlaceInBook(&C->Person, i);
PrintPerson(This, IDENT_NULL, &Typography);
}
fprintf(stderr, "\n");
PrintTitle("Projects", TermCols);
for(int i = 0; i < C->Project.ItemCount; ++i)
{
PrintProject(GetPlaceInBook(&C->Project, i), &Typography, 0, IndentationLevel, TermCols);
}
}
}
void
FreeProject(project *P)
{
FreeBook(&P->Medium);
FreeBook(&P->Indexer);
FreeBook(&P->Guest);
FreeBook(&P->CoHost);
for(int i = 0; i < P->Child.ItemCount; ++i)
{
FreeProject(GetPlaceInBook(&P->Child, i));
}
FreeBook(&P->Child);
FreeTemplate(&P->SearchTemplate);
FreeTemplate(&P->PlayerTemplate);
}
void
FreeConfig(config *C)
{
if(C)
{
for(int i = 0; i < C->Person.ItemCount; ++i)
{
person *Person = GetPlaceInBook(&C->Person, i);
FreeBook(&Person->Support);
}
FreeBook(&C->Person);
for(int i = 0; i < C->Project.ItemCount; ++i)
{
FreeProject(GetPlaceInBook(&C->Project, i));
}
FreeBook(&C->Project);
FreeBook(&C->ResolvedVariables);
FreeTemplate(&C->SearchTemplate);
Free(C);
}
}
config *
ParseConfig(string Path, memory_book *TokensList)
{
//PrintFunctionName("ParseConfig()");
config_type_specs TypeSpecs = InitTypeSpecs();
config *Result = 0;
tokens *T = Tokenise(TokensList, Path);
#if 0
MEM_LOOP_PRE_FREE("Tokenise")
FreeTokensList(TokensList);
//FreeAndResetCount(WatchHandles.Handle, WatchHandles.Count);
MEM_LOOP_PRE_WORK()
T = Tokenise(TokensList, Path);
MEM_LOOP_POST("Tokenise")
#endif
if(T)
{
scope_tree *ScopeTree = calloc(1, sizeof(scope_tree));
SetTypeSpec(ScopeTree, &TypeSpecs);
SetDefaults(ScopeTree, &TypeSpecs);
ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, 0);
// TODO(matt): Mem testing
//
#if 0
MEM_LOOP_PRE_FREE("ScopeTree")
FreeScopeTree(ScopeTree);
FreeTokensList(TokensList);
MEM_LOOP_PRE_WORK()
T = Tokenise(TokensList, Path);
ScopeTree = calloc(1, sizeof(scope_tree));
SetTypeSpec(ScopeTree, &TypeSpecs);
SetDefaults(ScopeTree, &TypeSpecs);
ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, 0);
MEM_LOOP_POST("ScopeTree")
#endif
//
////
//PrintScopeTree(ScopeTree);
if(ScopeTree)
{
//PrintC(CS_GREEN, "Resolving scope tree\n");
Result = ResolveVariables(ScopeTree);
#if 0
MEM_LOOP_PRE_FREE("ResolveVariables")
FreeConfig(Result);
MEM_LOOP_PRE_WORK()
Result = ResolveVariables(ScopeTree);
MEM_LOOP_POST("ResolveVariables")
#endif
FreeScopeTree(ScopeTree);
}
FreeTokensList(TokensList);
}
FreeTypeSpecs(&TypeSpecs);
return Result;
}
//
// config
#if 0
int
main(int ArgC, char **Args)
{
//fprintf(stderr, "%s\n", ConfigFile.Buffer.Location);
config_type_specs TypeSpecs = InitTypeSpecs();
config *Config = ParseConfig(&TypeSpecs, Wrap0("cinera.conf"));
if(Config)
{
//PrintConfig(Config);
FreeConfig(Config);
}
FreeTypeSpecs(&TypeSpecs);
_exit(0);
}
#endif