Annotation-System/cinera/cinera_config.c

5399 lines
188 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
//
#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); Print(stderr, "%s:%d MakeString()", Filename, LineNumber); Colourise(CS_END);
Print(stderr,
"\n"
" MakeString() has been passed a format string containing ");
Colourise(CS_BLUE_BOLD); Print(stderr, "%d specifier%s", FormatLength, FormatLength == 1 ? "" : "s"); Colourise(CS_END);
Print(stderr,
",\n"
" and ");
Colourise(CS_BLUE_BOLD); Print(stderr, "%d argument%s", ArgCount, ArgCount == 1 ? "" : "s"); Colourise(CS_END);
Print(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); Print(stderr, "%s:%d", Filename, LineNumber); Colourise(CS_END);
Print(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); Print(stderr, "%s:%d MakeString0()", Filename, LineNumber); Colourise(CS_END);
Print(stderr,
"\n"
" MakeString0() has been passed a format string containing ");
Colourise(CS_BLUE_BOLD); Print(stderr, "%d specifier%s", FormatLength, FormatLength == 1 ? "" : "s"); Colourise(CS_END);
Print(stderr,
",\n"
" and ");
Colourise(CS_BLUE_BOLD); Print(stderr, "%d argument%s", ArgCount, ArgCount == 1 ? "" : "s"); Colourise(CS_END);
Print(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); Print(stderr, "%s:%d", Filename, LineNumber); Colourise(CS_END);
Print(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 = MakeSpaceInBook(&Set->Token);
This->Type = Token->Type;
This->LineNumber = Set->CurrentLine;
if(Token->Type == TOKEN_NUMBER)
{
This->int64_t = StringToInt(Token->Content);
}
else
{
This->Content = Token->Content;
}
}
}
void
PrintToken(token *T, uint64_t Index)
{
Print(stderr, "[%lu, %s] ", Index, TokenTypeStrings[T->Type]);
if(T->Type == TOKEN_NUMBER)
{
Colourise(CS_BLUE_BOLD);
Print(stderr, "%li", T->int64_t);
Colourise(CS_END);
}
else if(T->Type == TOKEN_COMMENT)
{
PrintStringC(CS_BLACK_BOLD, T->Content);
}
else
{
PrintString(T->Content);
}
Print(stderr, "\n");
}
void
PrintTokens(tokens *T)
{
//PrintFunctionName("PrintTokens()");
for(int i = 0; i < T->Token.ItemCount; ++i)
{
PrintToken(GetPlaceInBook(&T->Token, i), i);
}
}
tokens *
PushTokens(memory_book *TokensList)
{
tokens *This = MakeSpaceInBook(TokensList);
This->Token = InitBook(sizeof(token), 16);
This->CurrentLine = 1;
return This;
}
void
PopTokens(memory_book *TokensList)
{
--TokensList->ItemCount;
tokens *This = GetPlaceInBook(TokensList, TokensList->ItemCount);
FreeBook(&This->Token);
This->CurrentLine = 0;
}
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);
FreeFileBufferAndPath(&This->File); // NOTE(matt): Not closing file because the next time we touch this handle is in
// RemoveAndFreeWatchHandles() where we will close it
}
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 enum
{
AS_INITIAL,
AS_GIVEN_OR_NICKNAME,
AS_DOTTED_INITIAL_AND_SURNAME,
AS_NONE,
AS_COUNT
} abbreviation_scheme;
typedef struct
{
string ID;
string Name;
// TODO(matt): string SortName;
string Abbreviations[AS_COUNT];
string QuoteUsername;
string Homepage;
_memory_book(support) Support;
} person;
typedef struct
{
string ID;
string Name;
string Plural;
bool NonSpeaking;
} role;
typedef struct
{
person *Person;
role *Role;
} credit;
typedef struct project
{
string ID;
string Lineage;
string WrittenLineage;
string Title;
string HTMLTitle;
string HMMLDir;
string TemplatesDir;
string SearchTemplatePath;
string PlayerTemplatePath;
string DefaultCCLang;
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;
vod_platform VODPlatform;
bool DenyBespokeTemplates;
bool SingleBrowserTab;
bool IgnorePrivacy;
person *Owner;
_memory_book(credit *) Credit;
_memory_book(medium) Medium;
medium *DefaultMedium;
_memory_book(project) Child;
struct project *Parent;
genre Genre;
numbering Numbering;
db_project_index Index;
template PlayerTemplate;
template SearchTemplate;
} project;
typedef struct
{
string DatabaseLocation;
string CacheDir;
string InstanceTitle;
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;
bool SuppressingPrompts;
time_t PrivacyCheckInterval;
uint8_t LogLevel;
_memory_book(person) Person;
_memory_book(role) Role;
_memory_book(project) Project;
memory_book ResolvedVariables;
} config;
char *ExpandPath(string Path, string *RelativeToFile); // NOTE(matt): Forward declared. Consider reorganising the code?
watch_file *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, string *Filename, uint64_t LineNumber)
{
tokens *Result = 0;
watch_file *WatchFile = PushWatchHandle(Path, EXT_NULL, WT_CONFIG, 0, 0);
Result = PushTokens(TokensList);
Result->File = InitFile(0, &Path, EXT_NULL, TRUE);
switch(ReadFileIntoBuffer(&Result->File))
{
case RC_ERROR_FILE_LOCKED:
{
ConfigErrorLockedConfigLocation(Filename, LineNumber, &Path);
FreeFile(&Result->File, TRUE);
PopTokens(TokensList);
Result = 0;
} break;
case RC_ERROR_FILE:
{
ConfigErrorUnopenableConfigLocation(Filename, LineNumber, &Path);
FreeFile(&Result->File, TRUE);
PopTokens(TokensList);
Result = 0;
} break;
default:
{
WatchFile->Handle = Result->File.Handle;
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 };
string Filepath = Wrap0(Result->File.Path);
ConfigError(&Filepath, 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_MINUS], B))
{
T.Type = TOKEN_MINUS;
T.Content.Base = B->Ptr;
++T.Content.Length;
Advancement = 1;
}
else if(!StringsDifferS(TokenStrings[TOKEN_ASSIGN], B))
{
T.Type = TOKEN_ASSIGN;
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;
string Filepath = Wrap0(Result->File.Path);
if(Char.Base)
{
ConfigError(&Filepath, Result->CurrentLine, S_WARNING, "Unhandled character (ignored): ", &Char);
}
else
{
ConfigErrorInt(&Filepath, Result->CurrentLine, S_WARNING, "Malformed UTF-8 bytes encountered (skipped): ", Char.Length);
}
}
PushToken(Result, &T);
Advance(B, Advancement);
SkipWhitespace(Result, B);
}
} break;
}
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(Type == This->Type);
}
bool
ExpectToken(tokens *T, token_type Type, token *Dest)
{
bool Result = FALSE;
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
if(Type == This->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;
_memory_book(config_type_field) Field;
} config_type_spec;
config_type_spec *
PushTypeSpec(memory_book *TypeSpecs, config_identifier_id ID, bool IsPermeable)
{
//PrintFunctionName("PushTypeSpec()");
config_type_spec *Result = MakeSpaceInBook(TypeSpecs);
Result->ID = ID;
Result->Field = InitBook(sizeof(config_type_field), 4);
Result->Permeable = IsPermeable;
return Result;
}
void
PushTypeSpecField(config_type_spec *Spec, config_field_type Type, config_identifier_id ID, bool Singleton)
{
//PrintFunctionName("PushTypeSpecField()");
config_type_field *This = MakeSpaceInBook(&Spec->Field);
This->Type = Type;
This->ID = ID;
This->Singleton = Singleton;
This->IsSetLocally = FALSE;
}
_memory_book(config_type_specs)
InitTypeSpecs(void)
{
//PrintFunctionName("InitTypeSpecs()");
memory_book Result = InitBook(sizeof(config_type_spec), 16);
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_CC_LANG, 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_INSTANCE_TITLE, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_JS_PATH, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_LOG_LEVEL, 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_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);
//
////
// NOTE(matt): Numbering
PushTypeSpecField(Root, FT_STRING, IDENT_NUMBERING_FILENAME_PREFIX, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_NUMBERING_METHOD, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_NUMBERING_SCHEME, TRUE);
PushTypeSpecField(Root, FT_NUMBER, IDENT_NUMBERING_START, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_NUMBERING_UNIT, TRUE);
PushTypeSpecField(Root, FT_BOOLEAN, IDENT_NUMBERING_ZERO_PAD, 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_BOOLEAN, IDENT_SUPPRESS_PROMPTS, TRUE);
PushTypeSpecField(Root, FT_NUMBER, IDENT_PRIVACY_CHECK_INTERVAL, TRUE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_CREDIT, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_INCLUDE, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_MEDIUM, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_PERSON, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_ROLE, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_PROJECT, FALSE);
PushTypeSpecField(Root, FT_SCOPE, IDENT_SUPPORT, FALSE);
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_ABBREV_INITIAL, TRUE);
PushTypeSpecField(Person, FT_STRING, IDENT_ABBREV_GIVEN_OR_NICKNAME, TRUE);
PushTypeSpecField(Person, FT_STRING, IDENT_ABBREV_DOTTED_INITIAL_AND_SURNAME, TRUE);
PushTypeSpecField(Person, FT_STRING, IDENT_HOMEPAGE, TRUE);
PushTypeSpecField(Person, FT_STRING, IDENT_QUOTE_USERNAME, TRUE);
PushTypeSpecField(Person, FT_SCOPE, IDENT_SUPPORT, FALSE);
config_type_spec *Role = PushTypeSpec(&Result, IDENT_ROLE, FALSE);
PushTypeSpecField(Role, FT_STRING, IDENT_NAME, TRUE);
PushTypeSpecField(Role, FT_STRING, IDENT_PLURAL, TRUE);
PushTypeSpecField(Role, FT_NUMBER, IDENT_POSITION, TRUE);
PushTypeSpecField(Role, FT_BOOLEAN, IDENT_NON_SPEAKING, TRUE);
config_type_spec *Credit = PushTypeSpec(&Result, IDENT_CREDIT, FALSE);
PushTypeSpecField(Credit, FT_STRING, IDENT_ROLE, 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_DEFAULT_CC_LANG, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_DEFAULT_MEDIUM, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_GENRE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_HMML_DIR, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_OWNER, TRUE); // NOTE(matt): Do not remove, because ResolveLocalVariable() recognises it
PushTypeSpecField(Project, FT_STRING, IDENT_PLAYER_LOCATION, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_PLAYER_TEMPLATE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_QUERY_STRING, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_SEARCH_LOCATION, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_SEARCH_TEMPLATE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_TEMPLATES_DIR, TRUE);
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);
//
////
// NOTE(matt): Numbering
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_FILENAME_PREFIX, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_METHOD, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_SCHEME, TRUE);
PushTypeSpecField(Project, FT_NUMBER, IDENT_NUMBERING_START, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_UNIT, TRUE);
PushTypeSpecField(Project, FT_BOOLEAN, IDENT_NUMBERING_ZERO_PAD, 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_CREDIT, FALSE);
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);
Print(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((F->ID == IDENT_NAME) && ParentScopeID == IDENT_ROLE)
{
if(!ConfigIdentifiers[F->ID].IdentifierDescription_RoleDisplayed)
{
++IndentationLevel;
IndentedCarriageReturn(IndentationLevel);
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[F->ID].IdentifierDescription_Role));
ConfigIdentifiers[F->ID].IdentifierDescription_RoleDisplayed = TRUE;
--IndentationLevel;
}
}
else if((F->ID == IDENT_ROLE) && ParentScopeID == IDENT_CREDIT)
{
if(!ConfigIdentifiers[F->ID].IdentifierDescription_CreditDisplayed)
{
++IndentationLevel;
IndentedCarriageReturn(IndentationLevel);
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[F->ID].IdentifierDescription_Credit));
ConfigIdentifiers[F->ID].IdentifierDescription_CreditDisplayed = TRUE;
--IndentationLevel;
}
}
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->Field.ItemCount; ++i)
{
config_type_field *This = GetPlaceInBook(&S->Field, i);
if(!(S->ID == IDENT_NULL && This->Type == FT_SCOPE))
{
PrintTypeField(This, S->ID, IndentationLevel);
}
}
if(S->ID != IDENT_NULL) { --IndentationLevel; }
}
void
PrintTypeSpecs(memory_book *TypeSpecs, int IndentationLevel)
{
Print(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 < TypeSpecs->ItemCount; ++i)
{
PrintTypeSpec(GetPlaceInBook(TypeSpecs, i), IndentationLevel);
}
}
void
FreeTypeSpecs(memory_book *TypeSpecs)
{
for(int i = 0; i < TypeSpecs->ItemCount; ++i)
{
config_type_spec *This = GetPlaceInBook(TypeSpecs, i);
FreeBook(&This->Field);
}
FreeBook(TypeSpecs);
}
typedef struct
{
char *Filename;
uint64_t LineNumber;
} token_position;
typedef enum
{
PT_BOOL,
PT_INT64,
PT_STRING,
} pair_type;
typedef struct
{
config_identifier_id Key;
union
{
string String;
int64_t int64_t;
bool bool;
};
token_position Position;
pair_type Type;
} config_pair;
typedef struct scope_tree
{
config_pair ID;
_memory_book(scope_tree) Trees;
_memory_book(config_pair) Pairs;
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(memory_book *TypeSpecs, config_identifier_id ID)
{
config_type_spec *Result = 0;
for(int i = 0; i < TypeSpecs->ItemCount; ++i)
{
config_type_spec *This = GetPlaceInBook(TypeSpecs, i);
if(This->ID == ID)
{
Result = This;
break;
}
}
return Result;
}
void
SetTypeSpec(scope_tree *Type, memory_book *TypeSpecs)
{
config_type_spec *Spec = GetTypeSpec(TypeSpecs, Type->ID.Key);
//PrintTypeSpec(Spec);
Type->TypeSpec.ID = Spec->ID;
Type->TypeSpec.Permeable = Spec->Permeable;
Type->TypeSpec.Field = InitBook(sizeof(config_type_field), 4);
for(int i = 0; i < Spec->Field.ItemCount; ++i)
{
config_type_field *Src = GetPlaceInBook(&Spec->Field, i);
config_type_field *Dest = MakeSpaceInBook(&Type->TypeSpec.Field);
Dest->Type = Src->Type;
Dest->ID = Src->ID;
Dest->Singleton = Src->Singleton;
Dest->IsSetLocally = Src->IsSetLocally;
}
}
config_type_field *
CurrentFieldIsInSpec(config_type_spec *Spec, tokens *T)
{
config_type_field *Result = 0;
if(Spec)
{
token *Token = GetPlaceInBook(&T->Token, T->CurrentIndex);
for(int i = 0; i < Spec->Field.ItemCount; ++i)
{
config_type_field *Field = GetPlaceInBook(&Spec->Field, i);
if(!StringsDifferLv0(Token->Content, ConfigIdentifiers[Field->ID].String))
{
Result = Field;
break;
}
}
}
return Result;
}
typedef struct
{
string Filename;
memory_book Allow;
memory_book Deny;
} config_include;
bool
ParseMultiStringAssignment(tokens *T, memory_book *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;
}
string *This = MakeSpaceInBook(Dest);
*This = Token.Content;
Field->IsSetLocally = TRUE;
if(!ExpectToken(T, TOKEN_SEMICOLON, &Token))
{
return FALSE;
}
return TRUE;
}
void
PrintCurrentToken(colour_code C, tokens *T)
{
Colourise(C);
PrintToken(GetPlaceInBook(&T->Token, T->CurrentIndex), 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
FreeInclude(config_include *I)
{
FreeBook(&I->Allow);
FreeBook(&I->Deny);
free(I);
I = 0;
}
bool
IsStringEmpty(string *S)
{
return S ? S->Length == 0 : TRUE;
}
void
FreeScopeTreeRecursively(scope_tree *T)
{
FreeBook(&T->Pairs);
FreeBook(&T->TypeSpec.Field);
//Free(T->Position.Filename);
for(int i = 0; i < T->Trees.ItemCount; ++i)
{
FreeScopeTreeRecursively(GetPlaceInBook(&T->Trees, i));
}
FreeBook(&T->Trees);
}
void
FreeScopeTree(scope_tree *T)
{
FreeBook(&T->Pairs);
FreeBook(&T->TypeSpec.Field);
//Free(T->Position.Filename);
for(int i = 0; i < T->Trees.ItemCount; ++i)
{
FreeScopeTreeRecursively(GetPlaceInBook(&T->Trees, i));
}
FreeBook(&T->Trees);
Free(T);
}
void
PrintPair(config_pair *P, char *Delimiter, bool FillSyntax, uint64_t Indentation, bool PrependNewline, bool AppendNewline)
{
if(PrependNewline) { Print(stderr, "\n"); }
Indent(Indentation);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[P->Key].String);
Print(stderr, "%s", Delimiter);
if(P->Type == PT_BOOL)
{
if(P->bool == TRUE)
{
Colourise(CS_GREEN);
if(FillSyntax) { Print(stderr, "\""); }
Print(stderr, "true");
if(FillSyntax) { Print(stderr, "\""); }
}
else
{
Colourise(CS_RED);
if(FillSyntax) { Print(stderr, "\""); }
Print(stderr, "false");
if(FillSyntax) { Print(stderr, "\""); }
}
Colourise(CS_END);
if(FillSyntax) { Print(stderr, ";"); }
}
else if(P->Type == PT_INT64)
{
switch(P->Key)
{
case IDENT_GENRE:
{
Colourise(CS_GREEN_BOLD);
if(FillSyntax) { Print(stderr, "\""); }
Print(stderr, "%s", GenreStrings[P->int64_t]);
if(FillSyntax) { Print(stderr, "\""); }
} break;
case IDENT_LOG_LEVEL:
{
Colourise(CS_GREEN_BOLD);
if(FillSyntax) { Print(stderr, "\""); }
Print(stderr, "%s", LogLevelStrings[P->int64_t]);
if(FillSyntax) { Print(stderr, "\""); }
} break;
case IDENT_NUMBERING_SCHEME:
{
Colourise(CS_GREEN_BOLD);
if(FillSyntax) { Print(stderr, "\""); }
Print(stderr, "%s", NumberingSchemeStrings[P->int64_t]);
if(FillSyntax) { Print(stderr, "\""); }
} break;
default:
{
Colourise(CS_BLUE_BOLD);
Print(stderr, "%li", P->int64_t);
} break;
}
Colourise(CS_END);
if(FillSyntax) { Print(stderr, ";"); }
}
else if(P->Type == PT_STRING)
{
if(P->String.Length)
{
Colourise(CS_GREEN_BOLD);
if(FillSyntax) { Print(stderr, "\""); }
PrintString(P->String);
if(FillSyntax) { Print(stderr, "\""); }
Colourise(CS_END);
if(FillSyntax) { Print(stderr, ";"); }
}
else
{
PrintC(CS_YELLOW, "[unset]");
}
}
if(AppendNewline) { Print(stderr, "\n"); }
}
void
PrintScopeID(config_pair *P, char *Delimiter, uint64_t Indentation)
{
Print(stderr, "\n");
Indent(Indentation);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[P->Key].String);
Print(stderr, "%s", Delimiter);
if(P->String.Length)
{
Colourise(CS_GREEN_BOLD);
Print(stderr, "\"");
PrintString(P->String);
Print(stderr, "\"");
Colourise(CS_END);
IndentedCarriageReturn(Indentation);
Print(stderr, "{");
}
else
{
PrintC(CS_YELLOW, "[unset]");
}
}
void
PrintBool(char *Title, bool Bool, char *Delimiter, bool FillSyntax, uint64_t Indentation, bool PrependNewline, bool AppendNewline)
{
if(PrependNewline) { Print(stderr, "\n"); }
Indent(Indentation);
PrintC(CS_YELLOW_BOLD, Title);
Print(stderr, "%s", Delimiter);
if(Bool == TRUE)
{
Colourise(CS_GREEN);
if(FillSyntax) { Print(stderr, "\""); }
Print(stderr, "true");
if(FillSyntax) { Print(stderr, "\""); }
}
else
{
Colourise(CS_RED);
if(FillSyntax) { Print(stderr, "\""); }
Print(stderr, "false");
if(FillSyntax) { Print(stderr, "\""); }
}
Colourise(CS_END);
if(AppendNewline) { Print(stderr, "\n"); }
}
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->Pairs.ItemCount; ++i)
{
PrintPair(GetPlaceInBook(&T->Pairs, i), " = ", ShouldFillSyntax, IndentationLevel, ShouldPrependNewline, FALSE);
}
for(int i = 0; i < T->Trees.ItemCount; ++i)
{
PrintScopeTree(GetPlaceInBook(&T->Trees, i), IndentationLevel);
}
if(T->ID.Key != IDENT_NULL)
{
--IndentationLevel;
IndentedCarriageReturn(IndentationLevel);
Print(stderr, "}");
}
}
}
void
PushPair(scope_tree *Parent, config_pair *P)
{
config_pair *New = MakeSpaceInBook(&Parent->Pairs);
*New = *P;
}
config_pair *
GetPair(scope_tree *Parent, config_identifier_id FieldID)
{
config_pair *Result = 0;
for(int i = 0; i < Parent->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&Parent->Pairs, i);
if(This->Key == FieldID)
{
Result = This;
break;
}
}
return Result;
}
void
InitScopeBooks(scope_tree *Scope)
{
Scope->Trees = InitBook(sizeof(scope_tree), 4);
Scope->Pairs = InitBook(sizeof(config_pair), 4);
}
scope_tree *
InitRootScopeTree(void)
{
scope_tree *Result = calloc(1, sizeof(scope_tree));
InitScopeBooks(Result);
return Result;
}
scope_tree *
PushScope(memory_book *TypeSpecs, scope_tree *Parent, config_pair *ID)
{
scope_tree *This = MakeSpaceInBook(&Parent->Trees);
InitScopeBooks(This);
This->ID = *ID;
SetTypeSpec(This, TypeSpecs);
This->Parent = Parent;
return(This);
}
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)
{
config_type_field *Result = 0;
for(int i = 0; i < Spec->Field.ItemCount; ++i)
{
config_type_field *This = GetPlaceInBook(&Spec->Field, i);
if(ID == This->ID)
{
Result = This;
break;
}
}
return Result;
}
bool
PairsMatch(config_pair *A, config_pair *B)
{
bool Result = FALSE;
if(A->Type == B->Type && A->Key == B->Key)
{
switch(A->Type)
{
case PT_BOOL:
{
Result = A->bool == B->bool;
} break;
case PT_INT64:
{
Result = A->int64_t == B->int64_t;
} break;
case PT_STRING:
{
Result = StringsMatch(A->String, B->String);
} break;
}
}
return Result;
}
scope_tree *
GetScope(scope_tree *T, config_pair *ID, bool IsSingleton)
{
scope_tree *Result = 0;
for(int i = 0; i < T->Trees.ItemCount; ++i)
{
scope_tree *This = GetPlaceInBook(&T->Trees, i);
if(IsSingleton)
{
if(ID->Key == This->ID.Key)
{
Result = This;
break;
}
}
else
{
if(PairsMatch(ID, &This->ID))
{
Result = This;
break;
}
}
}
return Result;
}
config_pair *
GetAssignment(scope_tree *T, config_pair *Assignment, bool IsSingleton)
{
config_pair *Result = 0;
for(int i = 0; i < T->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&T->Pairs, i);
if(IsSingleton)
{
if(Assignment->Type == This->Type && Assignment->Key == This->Key)
{
Result = This;
break;
}
}
else
{
if(PairsMatch(Assignment, This))
{
Result = This;
break;
}
}
}
return Result;
}
scope_tree *CopyScope(memory_book *TypeSpecs, scope_tree *Dest, scope_tree *Src);
void
CopyScopeContents(memory_book *TypeSpecs, scope_tree *Dest, scope_tree *Src)
{
for(int i = 0; i < Src->Pairs.ItemCount; ++i)
{
config_pair *Pair = GetPlaceInBook(&Src->Pairs, i);
PushPair(Dest, Pair);
}
for(int i = 0; i < Src->Trees.ItemCount; ++i)
{
scope_tree *Tree = GetPlaceInBook(&Src->Trees, i);
CopyScope(TypeSpecs, Dest, Tree);
}
}
scope_tree *
CopyScope(memory_book *TypeSpecs, scope_tree *Dest, scope_tree *Src)
{
Dest = PushScope(TypeSpecs, Dest, &Src->ID);
CopyScopeContents(TypeSpecs, Dest, Src);
return Dest;
}
void
MergeScopes(memory_book *TypeSpecs, scope_tree *Dest, scope_tree *Src, bool Intergenerational)
{
if(!Intergenerational || Dest->TypeSpec.Permeable)
{
for(int i = 0; i < Src->Pairs.ItemCount; ++i)
{
config_pair *Pair = GetPlaceInBook(&Src->Pairs, i);
config_type_field *Field = FieldIsInSpec(&Dest->TypeSpec, Pair->Key);
if(Field)
{
config_pair *ThisAssignment = GetAssignment(Dest, Pair, Field->Singleton);
if(!ThisAssignment) { PushPair(Dest, Pair); }
}
}
for(int i = 0; i < Src->Trees.ItemCount; ++i)
{
scope_tree *Tree = GetPlaceInBook(&Src->Trees, i);
config_type_field *Field = FieldIsInSpec(&Dest->TypeSpec, Tree->ID.Key);
if(Field && !(Intergenerational && Tree->ID.Key == Dest->ID.Key))
{
scope_tree *ThisAssignment = GetScope(Dest, &Tree->ID, Field->Singleton);
if(!ThisAssignment) { CopyScope(TypeSpecs, Dest, Tree); }
}
}
}
}
typedef enum
{
CRT_NULL,
CRT_ALLOW,
CRT_DENY,
} config_rule_type;
typedef struct
{
config_rule_type Scheme;
_memory_book(config_identifier_id) ID;
} config_include_rules;
void
PushRule(config_include_rules *R, config_identifier_id ID)
{
for(int i = 0; i < R->ID.ItemCount; ++i)
{
config_identifier_id *This = GetPlaceInBook(&R->ID, i);
if(ID == *This)
{
return;
}
}
config_identifier_id *This = MakeSpaceInBook(&R->ID);
*This = ID;
}
void
ParseRuleString(string *Filename, memory_book *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->Field.ItemCount; ++j)
{
config_type_field *ParentField = GetPlaceInBook(&ParentTypeSpec->Field, j);
PushRule(Rules, ParentField->ID);
config_type_spec *ThisTypeSpec = GetTypeSpec(TypeSpecs, ParentField->ID);
if(ThisTypeSpec)
{
for(int k = 0; k < ThisTypeSpec->Field.ItemCount; ++k)
{
config_type_field *ThisField = GetPlaceInBook(&ThisTypeSpec->Field, k);
PushRule(Rules, ThisField->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(memory_book *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;)
{
string Filepath = Wrap0(T->File.Path);
if(TokenIs(T, TOKEN_IDENTIFIER))
{
if(TokenEquals(T, "allow"))
{
if(Rules->Scheme == CRT_DENY)
{
FreeBook(&Rules->ID);
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
ConfigError(&Filepath, 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)
{
FreeBook(&Rules->ID);
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
ConfigError(&Filepath, 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(&Filepath, This->LineNumber, S_WARNING, "Invalid identifier in \"include\" scope: ", &This->Content);
Print(stderr,
" Valid identifiers:\n"
" allow\n"
" deny\n");
FreeBook(&Rules->ID);
return DepartIncludeAssignment(T, IncludeIdentifierTokenIndex) ? RC_INVALID_IDENTIFIER : RC_SYNTAX_ERROR;
}
++T->CurrentIndex;
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeBook(&Rules->ID); return RC_SYNTAX_ERROR; }
token RuleString = {};
if(!ExpectToken(T, TOKEN_STRING, &RuleString)) { FreeBook(&Rules->ID); return RC_SYNTAX_ERROR; }
ParseRuleString(&Filepath, TypeSpecs, ParentTypeSpec, Rules, &RuleString);
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeBook(&Rules->ID); return RC_SYNTAX_ERROR; }
}
else if(TokenIs(T, TOKEN_CLOSE_BRACE))
{
++T->CurrentIndex;
return RC_SUCCESS;
}
else
{
FreeBook(&Rules->ID);
return RC_SYNTAX_ERROR;
}
}
}
else
{
FreeBook(&Rules->ID);
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->ID.ItemCount; ++i)
{
config_identifier_id *This = GetPlaceInBook(&Rules->ID, i);
if(Field->ID == *This)
{
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->Field.ItemCount; ++i)
{
config_type_field *This = GetPlaceInBook(&S->Field, i);
This->IsSetLocally = FALSE;
}
}
scope_tree *
PushDefaultScope(memory_book *TypeSpecs, scope_tree *Parent, config_identifier_id Key, string Value)
{
config_pair ID = { .Key = Key, .String = Value, .Type = PT_STRING };
return PushScope(TypeSpecs, Parent, &ID);
}
void
PushDefaultPair(scope_tree *Parent, config_identifier_id Key, string Value)
{
config_pair Pair = { .Key = Key, .String = Value, .Type = PT_STRING };
return PushPair(Parent, &Pair);
}
void
PushDefaultIntPair(scope_tree *Parent, config_identifier_id Key, uint64_t Value)
{
config_pair IntPair = { .Key = Key, .int64_t = Value, .Type = PT_INT64 };
return PushPair(Parent, &IntPair);
}
void
PushDefaultBoolPair(scope_tree *Parent, config_identifier_id Key, bool Value)
{
config_pair BoolPair = { .Key = Key, .bool = Value, .Type = PT_BOOL };
return PushPair(Parent, &BoolPair);
}
#define DEFAULT_PRIVACY_CHECK_INTERVAL 60 * 4
void
SetDefaults(scope_tree *Root, memory_book *TypeSpecs)
{
scope_tree *SupportDonorbox = PushDefaultScope(TypeSpecs, Root, IDENT_SUPPORT, Wrap0("donorbox"));
PushDefaultPair(SupportDonorbox, IDENT_ICON, Wrap0("cinera_sprite_donorbox.png"));
PushDefaultIntPair(SupportDonorbox, IDENT_ICON_TYPE, IT_GRAPHICAL);
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 *SupportGitHub = PushDefaultScope(TypeSpecs, Root, IDENT_SUPPORT, Wrap0("github"));
PushDefaultPair(SupportGitHub, IDENT_ICON, Wrap0("cinera_sprite_github.png"));
PushDefaultIntPair(SupportGitHub, IDENT_ICON_TYPE, IT_GRAPHICAL);
PushDefaultIntPair(SupportGitHub, IDENT_ICON_VARIANTS, GetArtVariantFromString(Wrap0("normal")));
PushDefaultPair(SupportGitHub, IDENT_URL, Wrap0("https://github.com/sponsors/$person"));
scope_tree *SupportKofi = PushDefaultScope(TypeSpecs, Root, IDENT_SUPPORT, Wrap0("kofi"));
PushDefaultPair(SupportKofi, IDENT_ICON, Wrap0("cinera_sprite_kofi.png"));
PushDefaultIntPair(SupportKofi, IDENT_ICON_TYPE, IT_GRAPHICAL);
PushDefaultPair(SupportKofi, IDENT_URL, Wrap0("https://ko-fi.com/$person"));
scope_tree *SupportSendOwl = PushDefaultScope(TypeSpecs, Root, IDENT_SUPPORT, Wrap0("sendowl"));
PushDefaultPair(SupportSendOwl, IDENT_ICON, Wrap0("cinera_sprite_sendowl.png"));
PushDefaultIntPair(SupportSendOwl, IDENT_ICON_TYPE, IT_GRAPHICAL);
scope_tree *MediumAuthored = PushDefaultScope(TypeSpecs, Root, IDENT_MEDIUM, Wrap0("authored"));
PushDefaultPair(MediumAuthored, IDENT_ICON, Wrap0("&#128490;"));
PushDefaultPair(MediumAuthored, IDENT_NAME, Wrap0("Chat Comment"));
scope_tree *RoleIndexer = PushDefaultScope(TypeSpecs, Root, IDENT_ROLE, Wrap0("indexer"));
PushDefaultPair(RoleIndexer, IDENT_NAME, Wrap0("Indexer"));
PushDefaultBoolPair(RoleIndexer, IDENT_NON_SPEAKING, TRUE);
PushDefaultIntPair(RoleIndexer, IDENT_POSITION, -1);
PushDefaultPair(Root, IDENT_QUERY_STRING, Wrap0("r"));
PushDefaultPair(Root, IDENT_THEME, Wrap0("$origin"));
PushDefaultPair(Root, IDENT_CACHE_DIR, Wrap0("$XDG_CACHE_HOME/cinera"));
PushDefaultPair(Root, IDENT_DB_LOCATION, Wrap0("$XDG_CONFIG_HOME/cinera/cinera.db"));
// NOTE(matt): Numbering
PushDefaultPair(Root, IDENT_NUMBERING_FILENAME_PREFIX, Wrap0("$project"));
PushDefaultIntPair(Root, IDENT_NUMBERING_METHOD, NM_FILENAME_DERIVED);
PushDefaultIntPair(Root, IDENT_NUMBERING_SCHEME, NS_LINEAR);
PushDefaultIntPair(Root, IDENT_NUMBERING_START, 1);
PushDefaultBoolPair(Root, IDENT_NUMBERING_ZERO_PAD, FALSE);
//
// TODO(matt): Consider where the genre setting should apply, project vs entry level
PushDefaultIntPair(Root, IDENT_GENRE, GENRE_VIDEO);
PushDefaultIntPair(Root, IDENT_PRIVACY_CHECK_INTERVAL, DEFAULT_PRIVACY_CHECK_INTERVAL);
PushDefaultIntPair(Root, IDENT_LOG_LEVEL, LOG_ERROR);
}
void
AssignOrPushEnumValue(scope_tree *Parent, config_pair *Pair, bool IsSingleton, int Value)
{
Pair->int64_t = Value;
Pair->Type = PT_INT64;
config_pair *Assignment = 0;
if((Assignment = GetAssignment(Parent, Pair, IsSingleton)))
{
Assignment->int64_t = Pair->int64_t;
}
else
{
PushPair(Parent, Pair);
}
}
scope_tree *
ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *TypeSpecs, config_include_rules *Rules)
{
scope_tree *Parent = Tree;
for(T->CurrentIndex = 0; T->CurrentIndex < T->Token.ItemCount;)
{
string Filepath = Wrap0(T->File.Path);
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(&Filepath, 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 = {};
Pair.Key = Field->ID;
Pair.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;
Pair.Type = PT_STRING;
++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; }
Pair.Position.LineNumber = Value.LineNumber;
Pair.Type = PT_BOOL;
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
bool Bool = GetBoolFromString(&Filepath, &Value);
if(Bool != -1)
{
Pair.bool = Bool;
config_pair *Assignment = 0;
if((Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
{
Assignment->bool = Pair.bool;
}
else
{
PushPair(Parent, &Pair);
}
}
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;
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
if(Field->ID == IDENT_NUMBERING_METHOD)
{
numbering_method NumberingMethod = GetNumberingMethodFromString(&Filepath, &Value);
if(NumberingMethod != NM_COUNT)
{
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, NumberingMethod);
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_NUMBERING_SCHEME)
{
numbering_scheme NumberingScheme = GetNumberingSchemeFromString(&Filepath, &Value);
if(NumberingScheme != NS_COUNT)
{
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, NumberingScheme);
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_LOG_LEVEL)
{
log_level LogLevel = GetLogLevelFromString(&Filepath, &Value);
if(LogLevel != LOG_COUNT)
{
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, LogLevel);
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_GENRE)
{
genre Genre = GetGenreFromString(&Filepath, &Value);
if(Genre != GENRE_COUNT)
{
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, Genre);
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_ICON_TYPE)
{
icon_type IconType = GetIconTypeFromString(&Filepath, &Value);
if(IconType != IT_COUNT)
{
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, IconType);
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_VOD_PLATFORM)
{
vod_platform VODPlatform = GetVODPlatformFromString(&Filepath, &Value);
if(VODPlatform != VP_COUNT)
{
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, VODPlatform);
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_ART_VARIANTS || Field->ID == IDENT_ICON_VARIANTS)
{
int64_t ArtVariants = ParseArtVariantsString(&Filepath, &Value);
if(ArtVariants != -1)
{
Pair.int64_t = ArtVariants;
Pair.Type = PT_INT64;
config_pair *Assignment = 0;
if((Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
{
Assignment->int64_t = Pair.int64_t;
}
else
{
PushPair(Parent, &Pair);
}
}
else { FreeScopeTree(Tree); return 0; }
}
else
{
Pair.String = Value.Content;
Pair.Type = PT_STRING;
config_pair *Assignment = 0;
if((Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
{
Assignment->String = Pair.String;
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 = {};
bool IsNegative = FALSE;
if(TokenIs(T, TOKEN_MINUS))
{
IsNegative = TRUE;
++T->CurrentIndex;
}
if(!ExpectToken(T, TOKEN_NUMBER, &Value)) { FreeScopeTree(Tree); return 0; }
Pair.int64_t = IsNegative ? 0 - Value.int64_t : Value.int64_t;
Pair.Type = PT_INT64;
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
config_pair *Assignment = 0;
if((Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
{
Assignment->int64_t = Pair.int64_t;
}
else
{
PushPair(Parent, &Pair);
}
} 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.Type = PT_STRING;
Pair.String = Value.Content;
Pair.Position.LineNumber = Value.LineNumber;
if(Field->ID == IDENT_INCLUDE)
{
int IncludePathTokenIndex = T->CurrentIndex - 1;
config_include_rules Rules = {};
Rules.ID = InitBook(sizeof(config_identifier_id), 4);
switch(ParseIncludeRules(TypeSpecs, &Parent->TypeSpec, T, &Rules))
{
case RC_SUCCESS:
{
string IncluderFilePath = Wrap0(T->File.Path);
char *IncludePath = ExpandPath(Pair.String, &IncluderFilePath);
if(IncludePath)
{
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)
{
token *This = GetPlaceInBook(&T->Token, IncludePathTokenIndex);
I = Tokenise(TokensList, IncludePathL, &Filepath, This->LineNumber);
}
if(I)
{
Parent = ScopeTokens(Parent, TokensList, I, TypeSpecs, &Rules);
FreeBook(&Rules.ID);
if(!Parent)
{
return 0;
}
}
Free(IncludePath);
}
else
{
ConfigError(&IncluderFilePath, Pair.Position.LineNumber, S_WARNING, "Unable to include file: ", &Pair.String);
}
} break;
case RC_SYNTAX_ERROR:
{
FreeScopeTree(Tree); return 0;
} break;
case RC_SCHEME_MIXTURE:
case RC_INVALID_IDENTIFIER:
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_pair Hidden = {};
Hidden.Key = IDENT_HIDDEN;
Hidden.bool = FALSE;
Hidden.Type = PT_BOOL;
PushPair(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(&Filepath, This->LineNumber, S_WARNING, "Field not allowed: ", &This->Content);
DepartAssignment(T);
}
}
else
{
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
ConfigError(&Filepath, 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(&Filepath, 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
{
_memory_book(resolution_error) Errors;
_memory_book(resolution_error) Warnings;
} resolution_errors;
void
PushError(resolution_errors *E, severity Severity, token_position *Position, config_identifier_id Variable)
{
if(Severity == S_WARNING)
{
resolution_error *This = MakeSpaceInBook(&E->Warnings);
if(Position)
{
This->Position = *Position;
}
This->Variable = Variable;
}
else
{
resolution_error *This = MakeSpaceInBook(&E->Errors);
if(Position)
{
This->Position = *Position;
}
This->Variable = Variable;
}
}
resolution_error *
GetError(resolution_errors *E, severity Severity, token_position *Position, config_identifier_id Variable)
{
resolution_error *Result = 0;
if(Severity == S_WARNING)
{
for(int i = 0; i < E->Warnings.ItemCount; ++i)
{
resolution_error *This = GetPlaceInBook(&E->Warnings, i);
if(Position->Filename == This->Position.Filename &&
Position->LineNumber == This->Position.LineNumber &&
Variable == This->Variable)
{
Result = This;
break;
}
}
}
else
{
for(int i = 0; i < E->Errors.ItemCount; ++i)
{
resolution_error *This = GetPlaceInBook(&E->Errors, i);
if(Position->Filename == This->Position.Filename &&
Position->LineNumber == This->Position.LineNumber &&
Variable == This->Variable)
{
Result = This;
break;
}
}
}
return Result;
}
void
FreeErrors(resolution_errors *E)
{
FreeAndReinitialiseBook(&E->Errors);
FreeAndReinitialiseBook(&E->Warnings);
}
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;
}
role *
GetRole(config *C, resolution_errors *E, config_pair *RoleID)
{
role *Result = 0;
for(int i = 0; i < C->Role.ItemCount; ++i)
{
role *Role = GetPlaceInBook(&C->Role, i);
if(!StringsDifferCaseInsensitive(Role->ID, RoleID->String))
{
Result = Role;
break;
}
}
if(!Result && !GetError(E, S_WARNING, &RoleID->Position, IDENT_ROLE))
{
string Filepath = Wrap0(RoleID->Position.Filename);
ConfigError(&Filepath, RoleID->Position.LineNumber, S_WARNING, "Could not find role: ", &RoleID->String);
PushError(E, S_WARNING, &RoleID->Position, IDENT_ROLE);
WaitForInput();
}
return Result;
}
role *
GetRoleByID(config *C, string ID)
{
role *Result = 0;
for(int i = 0; i < C->Role.ItemCount; ++i)
{
role *Role = GetPlaceInBook(&C->Role, i);
if(!StringsDifferCaseInsensitive(Role->ID, ID))
{
Result = Role;
break;
}
}
if(!Result)
{
ConfigError(0, 0, S_WARNING, "Could not find role: ", &ID);
WaitForInput();
}
return Result;
}
person *
GetPerson(config *C, resolution_errors *E, config_pair *PersonID)
{
person *Result = 0;
for(int i = 0; i < C->Person.ItemCount; ++i)
{
person *Person = GetPlaceInBook(&C->Person, i);
if(!StringsDifferCaseInsensitive(Person->ID, PersonID->String))
{
Result = Person;
break;
}
}
if(!Result && !GetError(E, S_WARNING, &PersonID->Position, IDENT_PERSON))
{
string Filepath = Wrap0(PersonID->Position.Filename);
ConfigError(&Filepath, PersonID->Position.LineNumber, S_WARNING, "Could not find person: ", &PersonID->String);
PushError(E, S_WARNING, &PersonID->Position, IDENT_PERSON);
}
return Result;
}
typedef enum
{
LT_INTERNAL_ID, // e.g. hms/2019/devon
LT_EXTERNAL_FULL, // e.g. Handmade Seattle / 2019 / Devon's Journey
} lineage_type;
string
DeriveLineageOfProject(config *C, scope_tree *Project, lineage_type Type)
{
string Result = {};
string NullTitle = Wrap0("(null)");
memory_book StringList = InitBookOfPointers(4);
if(Project)
{
string **Writer = MakeSpaceInBook(&StringList);
switch(Type)
{
case LT_INTERNAL_ID: { *Writer = &Project->ID.String; } break;
case LT_EXTERNAL_FULL: {
config_pair *Title = GetPair(Project, IDENT_TITLE);
if(Title) { *Writer = &Title->String; }
else { *Writer = &NullTitle; }
} break;
}
Project = Project->Parent;
}
string Divider = Wrap0(Type == LT_INTERNAL_ID ? "/" : " / ");
while(Project && Project->ID.Key == IDENT_PROJECT)
{
string **Writer = MakeSpaceInBook(&StringList);
*Writer = &Divider;
Writer = MakeSpaceInBook(&StringList);
switch(Type)
{
case LT_INTERNAL_ID: { *Writer = &Project->ID.String; } break;
case LT_EXTERNAL_FULL: {
config_pair *Title = GetPair(Project, IDENT_TITLE);
if(Title) { *Writer = &Title->String; }
else { *Writer = &NullTitle; }
} break;
}
Project = Project->Parent;
}
for(int i = StringList.ItemCount - 1; i >= 0; --i)
{
string **Reader = GetPlaceInBook(&StringList, i);
Result = ExtendStringInBook(&C->ResolvedVariables, **Reader);;
}
FreeBook(&StringList);
return Result;
}
string
EmptyString(void)
{
string Result = {};
return Result;
}
string
DeriveLineageWithoutOriginOfProject(config *C, scope_tree *Project)
{
string Result = {};
memory_book StringList = InitBookOfPointers(4);
if(Project->Parent && Project->Parent->ID.Key == IDENT_PROJECT)
{
string **Writer = MakeSpaceInBook(&StringList);
*Writer = &Project->ID.String;
Project = Project->Parent;
}
string Slash = Wrap0("/");
while(Project->Parent && Project->Parent->ID.Key == IDENT_PROJECT)
{
string **Writer = MakeSpaceInBook(&StringList);
*Writer = &Slash;
Writer = MakeSpaceInBook(&StringList);
*Writer = &Project->ID.String;
Project = Project->Parent;
}
if(StringList.ItemCount > 0)
{
for(int i = StringList.ItemCount - 1; i >= 0; --i)
{
string **Reader = GetPlaceInBook(&StringList, i);
Result = ExtendStringInBook(&C->ResolvedVariables, **Reader);
}
FreeBook(&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 = {};
string Filepath = Wrap0(Position->Filename);
switch(Variable)
{
case IDENT_LINEAGE:
{
if(Scope->ID.Key == IDENT_PROJECT)
{
Result = DeriveLineageOfProject(C, Scope, LT_INTERNAL_ID);
}
else
{
string This = Wrap0(ConfigIdentifiers[Variable].String);
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(&Filepath, 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(&Filepath, 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.String);
}
else
{
string This = Wrap0(ConfigIdentifiers[Variable].String);
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This);
PushError(E, S_WARNING, Position, Variable);
}
}
} break;
case IDENT_OWNER:
{
bool Processed = FALSE;
while(!Processed && Scope)
{
for(int i = 0; i < Scope->Pairs.ItemCount; ++i)
{
config_pair *Pair = GetPlaceInBook(&Scope->Pairs, i);
if(Variable == Pair->Key)
{
if(GetPerson(C, E, Pair))
{
Result = ExtendStringInBook(&C->ResolvedVariables, Pair->String);
Processed = TRUE;
break;
}
else
{
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Owner set, but the person does not exist: ", &Pair->String);
PushError(E, S_WARNING, Position, Variable);
}
Processed = TRUE;
}
}
}
Scope = Scope->Parent;
}
if(!Processed)
{
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(&Filepath, Position->LineNumber, S_WARNING, "No owner set", 0);
PushError(E, S_WARNING, Position, Variable);
}
}
} break;
case IDENT_PERSON:
{
// NOTE(matt): Allow scopes within a person scope, e.g. support, to resolve $person
while(Scope && 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.String);
}
else
{
string This = Wrap0(ConfigIdentifiers[Variable].String);
if(!GetError(E, S_WARNING, Position, Variable))
{
ConfigError(&Filepath, 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(&Filepath, 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->String.Length;)
{
switch(S->String.Base[i])
{
case '$':
{
string Test = {};
++i;
if(i < S->String.Length && S->String.Base[i] == '{')
{
++i;
Test.Base = S->String.Base + i;
while(i < S->String.Length && S->String.Base[i] != '}')
{
++Test.Length;
++i;
}
}
else
{
Test.Base = S->String.Base + i;
while(i < S->String.Length && IsValidIdentifierCharacter(S->String.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->String.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->String.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 uint64_t variant;
typedef struct
{
string Path;
_memory_book(variant) Variants;
} variant_string;
void
PushVariantUniquely(resolution_errors *E, memory_book *VariantStrings, string Path, variant Variants, config_identifier_id Identifier)
{
if(Path.Length > 0)
{
bool FoundPath = FALSE;
for(int i = 0; i < VariantStrings->ItemCount; ++i)
{
variant_string *ThisVariantString = GetPlaceInBook(VariantStrings, i);
if(StringsMatch(ThisVariantString->Path, Path))
{
FoundPath = TRUE;
bool FoundVariantsMatch = FALSE;
for(int VariantsIndex = 0; VariantsIndex < ThisVariantString->Variants.ItemCount; ++VariantsIndex)
{
variant Test = *(variant *)GetPlaceInBook(&ThisVariantString->Variants, VariantsIndex);
if(Test == Variants)
{
FoundVariantsMatch = TRUE;
}
}
if(!FoundVariantsMatch)
{
variant *ThisVariant = MakeSpaceInBook(&ThisVariantString->Variants);
*ThisVariant = Variants;
PushError(E, S_ERROR, 0, Identifier);
}
}
}
if(!FoundPath)
{
variant_string *NewVariantString = MakeSpaceInBook(VariantStrings);
NewVariantString->Path = Path;
NewVariantString->Variants = InitBook(sizeof(variant), 4);
variant *NewVariants = MakeSpaceInBook(&NewVariantString->Variants);
*NewVariants = Variants;
}
}
}
typedef struct
{
string String0;
string String1;
_memory_book(project *) Projects;
} config_string_association;
typedef struct
{
config_identifier_id ID0;
config_identifier_id ID1;
_memory_book(config_string_association) Associations;
} config_string_associations;
typedef struct
{
_memory_book(variant_string) 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 = MakeSpaceInBook(&P->Medium);
Medium->ID = ResolveString(C, E, MediumTree, &MediumTree->ID, FALSE);
for(int i = 0; i < MediumTree->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&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->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&MediumTree->Pairs, i);
switch(This->Key)
{
// int64_t
case IDENT_ICON_TYPE: { Medium->IconType = This->int64_t; } break;
case IDENT_ICON_VARIANTS: { Medium->IconVariants = This->int64_t; } break;
// bool
case IDENT_HIDDEN: { Medium->Hidden = This->bool; } 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;
}
}
}
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 = MakeSpaceInBook(&P->Support);
Support->ID = ResolveString(C, E, SupportTree, &SupportTree->ID, FALSE);
for(int i = 0; i < SupportTree->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&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->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&SupportTree->Pairs, i);
switch(This->Key)
{
case IDENT_ICON_TYPE: { Support->IconType = This->int64_t; } break;
case IDENT_ICON_VARIANTS: { Support->IconVariants = This->int64_t; } 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;
}
}
}
void
InitialSubstring(memory_book *Abbreviations,
string *Dest, string Src, int Extent, string PostInitialString)
{
int SrcIndex = 0;
bool InQuote = FALSE;
string Initial;
int StepSize = 1;
if(Src.Base[SrcIndex] == '\"')
{
InQuote = !InQuote;
}
else
{
Initial = GetUTF8Character(Src.Base + SrcIndex, Extent - SrcIndex);
*Dest = ExtendStringInBook(Abbreviations, Initial);
*Dest = ExtendStringInBook(Abbreviations, PostInitialString);
StepSize = Initial.Length;
}
SrcIndex += StepSize;
for(; SrcIndex < Extent; SrcIndex += StepSize)
{
StepSize = 1;
switch(Src.Base[SrcIndex])
{
case ' ':
{
if(!InQuote
&& SrcIndex + 1 < Extent && Src.Base[SrcIndex + 1] != '\"')
{
Initial = GetUTF8Character(Src.Base + SrcIndex + 1, Extent - SrcIndex - 1);
*Dest = ExtendStringInBook(Abbreviations, Initial);
*Dest = ExtendStringInBook(Abbreviations, PostInitialString);
StepSize = Initial.Length;
}
} break;
case '\"':
{
InQuote = !InQuote;
} break;
}
}
}
string
InitialString(memory_book *Abbreviations, string Src)
{
ResetPen(Abbreviations);
string Result = {};
InitialSubstring(Abbreviations, &Result, Src, Src.Length, Wrap0(""));
return Result;
}
string
GetQuotedOrFirstSubstring(string Src)
{
string Result;
string QuotedSubstring = {};
string FirstSubstring = Src;
bool GotFirstSubstring = FALSE;
bool InQuote = FALSE;
for(int SrcIndex = 0; SrcIndex < Src.Length && !QuotedSubstring.Length; ++SrcIndex)
{
switch(Src.Base[SrcIndex])
{
case ' ':
{
if(!InQuote && !GotFirstSubstring)
{
FirstSubstring.Base = Src.Base;
FirstSubstring.Length = SrcIndex;
GotFirstSubstring = TRUE;
}
} break;
case '\"':
{
if(!InQuote)
{
if(SrcIndex + 1 < Src.Length)
{
QuotedSubstring.Base = Src.Base + SrcIndex + 1;
}
}
else
{
QuotedSubstring.Length = SrcIndex - (QuotedSubstring.Base - Src.Base);
}
InQuote = !InQuote;
} break;
}
}
Result = QuotedSubstring.Length ? QuotedSubstring : FirstSubstring;
return Result;
}
string
StripQuotedStringStart(string S)
{
string Result = S;
if(Result.Length > 0 && Result.Base[0] == '\"')
{
++Result.Base;
--Result.Length;
while(Result.Length > 0 && Result.Base[0] != '\"')
{
++Result.Base;
--Result.Length;
}
Assert(Result.Length > 0);
++Result.Base;
--Result.Length;
}
return Result;
}
string
StripQuotedStringEnd(string S)
{
string Result = S;
if(Result.Length > 0 && Result.Base[Result.Length - 1] == '\"')
{
--Result.Length;
while(Result.Length > 0 && Result.Base[Result.Length - 1] != '\"')
{
--Result.Length;
}
Assert(Result.Length > 0);
--Result.Length;
}
return Result;
}
bool
IsLower(char C)
{
return C >= 'a' && C <= 'z';
}
string
DottedInitialAndGetSurname(memory_book *Abbreviations, string Src)
{
ResetPen(Abbreviations);
string Result = {};
bool GotSurnameBase = FALSE;
string WorkingSrc = Src;
WorkingSrc = StripQuotedStringStart(WorkingSrc);
WorkingSrc = StripQuotedStringEnd(WorkingSrc);
WorkingSrc = TrimWhitespace(WorkingSrc);
int PossibleSurnameBase = WorkingSrc.Length - 1;
int SurnameBase = PossibleSurnameBase;
for(; PossibleSurnameBase > 0; --PossibleSurnameBase)
{
char Prev = WorkingSrc.Base[PossibleSurnameBase - 1];
if(Prev == ' ')
{
if(!GotSurnameBase)
{
SurnameBase = PossibleSurnameBase;
}
else if(IsLower(WorkingSrc.Base[PossibleSurnameBase]))
{
SurnameBase = PossibleSurnameBase;
}
else
{
break;
}
GotSurnameBase = TRUE;
}
else if(Prev == '\"')
{
break;
}
}
if(!GotSurnameBase)
{
SurnameBase = PossibleSurnameBase;
}
if(SurnameBase > 0 && WorkingSrc.Base[SurnameBase] == ' ' && SurnameBase < WorkingSrc.Length)
{
++SurnameBase;
}
string Surname = Wrap0i_(WorkingSrc.Base + SurnameBase, WorkingSrc.Length - SurnameBase);
if(SurnameBase > 0)
{
InitialSubstring(Abbreviations, &Result, WorkingSrc, SurnameBase, Wrap0(". "));
}
Result = ExtendStringInBook(Abbreviations, Surname);
return Result;
}
void
AbbreviateName(config *C, person *P)
{
if(P->Abbreviations[AS_INITIAL].Length == 0)
{
P->Abbreviations[AS_INITIAL] = InitialString(&C->ResolvedVariables, P->Name);
}
if(P->Abbreviations[AS_GIVEN_OR_NICKNAME].Length == 0)
{
P->Abbreviations[AS_GIVEN_OR_NICKNAME] = GetQuotedOrFirstSubstring(P->Name);
}
if(P->Abbreviations[AS_DOTTED_INITIAL_AND_SURNAME].Length == 0)
{
P->Abbreviations[AS_DOTTED_INITIAL_AND_SURNAME] = DottedInitialAndGetSurname(&C->ResolvedVariables, P->Name);
}
P->Abbreviations[AS_NONE] = P->Name;
}
void
PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *PersonTree)
{
//PrintFunctionName("PushPersonOntoConfig()");
//PrintScopeTree(PersonTree);
person *This = MakeSpaceInBook(&C->Person);
This->ID = ResolveString(C, E, PersonTree, &PersonTree->ID, FALSE);
bool NamingError = FALSE;
for(int i = 0; i < PersonTree->Pairs.ItemCount; ++i)
{
config_pair *Pair = GetPlaceInBook(&PersonTree->Pairs, i);
if(IDENT_NAME == Pair->Key)
{
This->Name = ResolveString(C, E, PersonTree, Pair, FALSE);
int QuotemarkCount = StringContainsXOfChar(This->Name, '\"');
if(QuotemarkCount == 1)
{
string Filepath = Wrap0(Pair->Position.Filename);
ConfigErrorField(&Filepath, Pair->Position.LineNumber, S_ERROR, IDENT_NAME, &This->Name,
"contains unpaired quotation mark");
PushError(E, S_ERROR, 0, Pair->Key);
NamingError = TRUE;
}
else if(QuotemarkCount > 2)
{
string Filepath = Wrap0(Pair->Position.Filename);
ConfigErrorField(&Filepath, Pair->Position.LineNumber, S_ERROR, IDENT_NAME, &This->Name,
"contains more than one pair of quotation marks");
PushError(E, S_ERROR, 0, Pair->Key);
NamingError = TRUE;
}
}
else if(IDENT_HOMEPAGE == Pair->Key)
{
This->Homepage = ResolveString(C, E, PersonTree, Pair, FALSE);
}
else if(IDENT_QUOTE_USERNAME == Pair->Key)
{
This->QuoteUsername = ResolveString(C, E, PersonTree, Pair, FALSE);
}
}
if(This->Name.Length == 0)
{
string Filepath = Wrap0(PersonTree->ID.Position.Filename);
ConfigErrorUnsetFieldOf(&Filepath, PersonTree->ID.Position.LineNumber,
IDENT_NAME, IDENT_PERSON, This->ID);
PushError(E, S_ERROR, 0, IDENT_NAME);
NamingError = TRUE;
}
if(!NamingError)
{
AbbreviateName(C, This);
}
if(This->QuoteUsername.Length == 0)
{
This->QuoteUsername = This->ID;
}
This->Support = InitBook(sizeof(support), 2);
for(int i = 0; i < PersonTree->Trees.ItemCount; ++i)
{
scope_tree *ThisSupport = GetPlaceInBook(&PersonTree->Trees, i);
PushSupport(C, E, V, This, ThisSupport);
}
}
void
PushRoleOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *RoleTree)
{
role *This = MakeSpaceInBook(&C->Role);
This->ID = ResolveString(C, E, RoleTree, &RoleTree->ID, FALSE);
for(int i = 0; i < RoleTree->Pairs.ItemCount; ++i)
{
config_pair *Pair = GetPlaceInBook(&RoleTree->Pairs, i);
switch(Pair->Key)
{
case IDENT_NAME:
{
This->Name = ResolveString(C, E, RoleTree, Pair, FALSE);
} break;
case IDENT_PLURAL:
{
This->Plural = ResolveString(C, E, RoleTree, Pair, FALSE);
} break;
case IDENT_NON_SPEAKING:
{
This->NonSpeaking = Pair->bool;
} break;
default: break;
}
}
}
void
PushCredit(config *C, resolution_errors *E, project *P, scope_tree *CreditTree)
{
CreditTree->ID.String = ResolveString(C, E, CreditTree, &CreditTree->ID, FALSE);
if(CreditTree->ID.String.Base)
{
person *Person = GetPerson(C, E, &CreditTree->ID);
if(Person)
{
for(int i = 0; i < CreditTree->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&CreditTree->Pairs, i);
role *Role = GetRole(C, E, This);
if(Role)
{
credit *Credit = MakeSpaceInBook(&P->Credit);
// TODO(matt): Sort the P->Credit book by Person->SortName
Credit->Role = Role;
Credit->Person = Person;
}
}
}
}
}
void PushProjectOntoProject(config *C, resolution_errors *E, config_verifiers *Verifiers, project *Parent, scope_tree *ProjectTree);
void
AddProjectToAssociation(config_string_association *A, project *P)
{
project **This = MakeSpaceInBook(&A->Projects);
*This = P;
}
void
PushAssociation(config_string_associations *HMMLDirs, string *S0, string *S1, project *P)
{
config_string_association *This = MakeSpaceInBook(&HMMLDirs->Associations);
This->Projects = InitBookOfPointers(4);
if(S0) { This->String0 = *S0; }
if(S1) { This->String1 = *S1; }
AddProjectToAssociation(This, P);
}
config_string_association *
GetAssociation(config_string_associations *Set, string String0, string String1)
{
config_string_association *Result = 0;
for(int i = 0; i < Set->Associations.ItemCount; ++i)
{
config_string_association *This = GetPlaceInBook(&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->Associations.ItemCount; ++i)
{
config_string_association *This = GetPlaceInBook(&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->Associations.ItemCount; ++i)
{
config_string_association *This = GetPlaceInBook(&HMMLDirs->Associations, i);
if(StringsMatch(Result, This->String0))
{
AddProjectToAssociation(This, 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)
{
P->Medium = InitBook(sizeof(medium), 8);
P->Child = InitBook(sizeof(project), 8);
P->Credit = InitBook(sizeof(credit), 4);
config_string_associations *HMMLDirs = &V->HMMLDirs;
P->ID = ResolveString(C, E, ProjectTree, &ProjectTree->ID, FALSE);
if(P->ID.Length > MAX_PROJECT_ID_LENGTH)
{
string Filepath = Wrap0(ProjectTree->ID.Position.Filename);
ConfigErrorSizing(&Filepath, 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, LT_INTERNAL_ID);
// NOTE(matt): Initial pass over as-yet unresolvable local variable(s)
//
for(int i = 0; i < ProjectTree->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&ProjectTree->Pairs, i);
switch(This->Key)
{
case IDENT_OWNER:
{
// TODO(matt): Do we need to fail completely if owner cannot be found?
P->Owner = GetPerson(C, E, This);
} break;
default: break;
}
}
//
////
for(int i = 0; i < ProjectTree->Trees.ItemCount; ++i)
{
scope_tree *This = GetPlaceInBook(&ProjectTree->Trees, i);
switch(This->ID.Key)
{
case IDENT_MEDIUM:
{
PushMedium(C, E, V, P, This);
} break;
case IDENT_CREDIT:
{
PushCredit(C, E, P, This);
} break;
default: break;
}
}
for(int i = 0; i < ProjectTree->Pairs.ItemCount; ++i)
{
config_pair *This = GetPlaceInBook(&ProjectTree->Pairs, i);
string Filepath = Wrap0(This->Position.Filename);
switch(This->Key)
{
// NOTE(matt): String
case IDENT_DEFAULT_CC_LANG:
{
P->DefaultCCLang = ResolveString(C, E, ProjectTree, This, FALSE);
if(!IsValidLanguageCode(P->DefaultCCLang))
{
ConfigErrorField(&Filepath, This->Position.LineNumber, S_ERROR, This->Key, &P->DefaultCCLang,
"contains invalid character(s)");
PrintValidLanguageCodeChars();
PushError(E, S_ERROR, 0, This->Key);
}
} break;
case IDENT_DEFAULT_MEDIUM:
{ P->DefaultMedium = GetMediumFromProject(P, This->String); } break;
case IDENT_HMML_DIR:
{ SetUniqueHMMLDir(C, E, HMMLDirs, P, ProjectTree, 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(&Filepath, 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(&Filepath, 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_NUMBERING_FILENAME_PREFIX: { P->Numbering.FilenameDerived.Prefix = ResolveString(C, E, ProjectTree, This, FALSE); } break;
case IDENT_NUMBERING_UNIT: { P->Numbering.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(&Filepath, 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(&Filepath, 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(&Filepath, 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(&Filepath, 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_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;
// NOTE(matt): int64_t
case IDENT_GENRE: { P->Genre = This->int64_t; } break;
case IDENT_ART_VARIANTS: { P->ArtVariants = This->int64_t; } break;
case IDENT_ICON_TYPE: { P->IconType = This->int64_t; } break;
case IDENT_ICON_VARIANTS: { P->IconVariants = This->int64_t; } break;
case IDENT_NUMBERING_METHOD: { P->Numbering.Method = This->int64_t; } break;
case IDENT_NUMBERING_SCHEME: { P->Numbering.Scheme = This->int64_t; } break;
case IDENT_NUMBERING_START: { P->Numbering.Auto.StartingNumber = This->int64_t; } break;
case IDENT_VOD_PLATFORM: { P->VODPlatform = This->int64_t; } break;
// NOTE(matt): bool
case IDENT_DENY_BESPOKE_TEMPLATES: { P->DenyBespokeTemplates = This->bool; } break;
case IDENT_IGNORE_PRIVACY: { P->IgnorePrivacy = This->bool; } break;
case IDENT_NUMBERING_ZERO_PAD: { P->Numbering.Auto.ZeroPadded = This->bool; } break;
case IDENT_SINGLE_BROWSER_TAB: { P->SingleBrowserTab = This->bool; } break;
default: break;
}
}
ResetPen(&C->ResolvedVariables);
P->WrittenLineage = DeriveLineageOfProject(C, ProjectTree, LT_EXTERNAL_FULL);
for(int i = 0; i < ProjectTree->Trees.ItemCount; ++i)
{
scope_tree *This = GetPlaceInBook(&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 = MakeSpaceInBook(&Parent->Child);
PushProject(C, E, Verifiers, P, ProjectTree);
P->Parent = Parent;
}
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(ID, This->ID))
{
Result = This;
break;
}
}
}
return Result;
}
void
PushProjectOntoConfig(config *C, resolution_errors *E, config_verifiers *Verifiers, scope_tree *ProjectTree)
{
project *P = MakeSpaceInBook(&C->Project);
PushProject(C, E, Verifiers, P, ProjectTree);
}
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(ID, This->ID))
{
Result = This;
break;
}
}
}
return Result;
}
void
PrintAssociationClashes(config_string_associations *A)
{
for(int i = 0; i < A->Associations.ItemCount; ++i)
{
config_string_association *Association = GetPlaceInBook(&A->Associations, i);
if(Association->Projects.ItemCount > 1)
{
ConfigError(0, 0, S_ERROR, "Multiple projects share the same:", 0);
config_pair Assoc0 = { .Key = A->ID0, .String = Association->String0, .Type = PT_STRING };
int IndentLevel = 2;
PrintPair(&Assoc0, ": ", FALSE, IndentLevel, FALSE, FALSE);
if(A->ID1 != IDENT_NULL)
{
config_pair Assoc1 = { .Key = A->ID1, .String = Association->String1, .Type = PT_STRING };
PrintPair(&Assoc1, ": ", FALSE, IndentLevel, TRUE, FALSE);
}
Print(stderr, "\n");
for(int j = 0; j < Association->Projects.ItemCount; ++j)
{
project **P = GetPlaceInBook(&Association->Projects, j);
Print(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("]"));
}
Print(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)
{
Print(stderr, "%s", NeedsSpacing ? " " : "");
}
else
{
Print(stderr, "\n");
PrintString(Margin);
NeedsSpacing = FALSE;
RunningAvailableColumns = AvailableColumnsForValue;
}
PrintC(ColourCode, String);
RunningAvailableColumns -= Length + NeedsSpacing;
NeedsSpacing = TRUE;
}
}
}
else
{
PrintC(CS_YELLOW, "[unset]");
}
}
void
PrintVariantInconsistencies(memory_book *VariantStrings)
{
for(int i = 0; i < VariantStrings->ItemCount; ++i)
{
variant_string *ThisVariantsString = GetPlaceInBook(VariantStrings, i);
if(ThisVariantsString->Variants.ItemCount > 1)
{
ConfigError(0, 0, S_ERROR, "Sprite has inconsistently set variants ", &ThisVariantsString->Path);
for(int j = 0; j < ThisVariantsString->Variants.ItemCount; ++j)
{
string Margin = Wrap0(" ");
variant V = *(variant *)GetPlaceInBook(&ThisVariantsString->Variants, j);
PrintVariants(V, j % 2 ? CS_CYAN_BOLD : CS_YELLOW_BOLD, Margin);
Print(stderr, "\n");
}
}
}
}
void
FreeAssociations(config_string_associations *A)
{
for(int i = 0; i < A->Associations.ItemCount; ++i)
{
config_string_association *This = GetPlaceInBook(&A->Associations, i);
FreeBook(&This->Projects);
}
FreeBook(&A->Associations);
}
void
FreeVariants(memory_book *VariantStrings)
{
for(int i = 0; i < VariantStrings->ItemCount; ++i)
{
variant_string *This = GetPlaceInBook(VariantStrings, i);
FreeBook(&This->Variants);
}
FreeBook(VariantStrings);
}
void
FreeVerifiers(config_verifiers *Verifiers)
{
FreeAssociations(&Verifiers->BaseDirAndSearchLocation);
FreeAssociations(&Verifiers->BaseDirAndPlayerLocation);
FreeAssociations(&Verifiers->HMMLDirs);
FreeVariants(&Verifiers->VariantStrings);
}
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);
Print(stderr, "%s", Delimiter);
PrintC(CS_GREEN_BOLD, LogLevelStrings[Level]);
if(AppendNewline) { Print(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)
{
Print(stderr, "%s", T->Vertical);
}
}
void
CarriageReturn(typography *T, int VerticalsRequired)
{
Print(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);
//Print(stderr, "%s", T.Margin);
#if 0
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_ICON_TYPE].String);
Print(stderr, "%s", T.Delimiter);
PrintC(CS_GREEN_BOLD, IconTypeStrings[Type]);
Print(stderr, "\n");
#endif
if(PrependNewline)
{
CarriageReturn(T, Generation);
++Result;
}
Print(stderr, "%s", T->Margin);
config_identifier_id Key = IDENT_ICON_TYPE;
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[Key].String);
Print(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)
{
Print(stderr, "%s%s ", *RowsRequired == 1 ? T->LowerLeft : T->Vertical, T->Margin);
}
Print(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);
Print(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)
{
Print(stderr, "%s", NeedsSpacing ? " " : "");
}
else
{
CarriageReturn(T, Generation);
if(RowsRequired)
{
--*RowsRequired;
Print(stderr, "%s%s ", *RowsRequired == 1 ? T->LowerLeft : T->Vertical, T->Margin);
}
Print(stderr, "%s", T->Margin);
for(int i = 0; i < CharactersInKeyPlusDelimiter; ++i)
{
Print(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)
{
PrintStringC(CS_BLUE_BOLD, M->ID);
if(M->Hidden)
{
PrintC(CS_BLACK_BOLD, " [hidden]");
}
Print(stderr, "%s", Delimiter);
PrintStringC(CS_MAGENTA, M->Name);
Print(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);
Print(stderr, ")");
if(AppendNewline)
{
Print(stderr, "\n");
}
}
void
PrintSupport(support *S, typography *Typography, int IndentationLevel, uint8_t *RowsRequired)
{
bool ShouldFillSyntax = FALSE;
config_pair ID = { .Key = IDENT_SUPPORT, .String = S->ID, .Type = PT_STRING };
Print(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, .String = S->URL, .Type = PT_STRING };
Print(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, .String = S->Icon, .Type = PT_STRING };
Print(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, .String = S->IconNormal, .Type = PT_STRING };
Print(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, .String = S->IconFocused, .Type = PT_STRING };
Print(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
Print(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->Abbreviations[AS_INITIAL].Length > 0) { ++RowsRequired; }
if(P->Abbreviations[AS_GIVEN_OR_NICKNAME].Length > 0) { ++RowsRequired; }
if(P->Abbreviations[AS_DOTTED_INITIAL_AND_SURNAME].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, typography *Typography)
{
bool HaveInfo = P->Name.Length > 0 || P->Homepage.Length > 0 || P->Support.ItemCount > 0;
Print(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);
Print(stderr, "\n");
uint8_t RowsRequired = GetRowsRequiredForPersonInfo(Typography, P);
int IndentationLevel = 0;
bool ShouldFillSyntax = FALSE;
if(P->Name.Length > 0)
{
Print(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair Name = { .Key = IDENT_NAME, .String = P->Name, .Type = PT_STRING }; PrintPair(&Name, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
if(P->Abbreviations[AS_INITIAL].Length > 0)
{
Print(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair AbbrevInitial = { .Key = IDENT_ABBREV_INITIAL, .String = P->Abbreviations[AS_INITIAL], .Type = PT_STRING }; PrintPair(&AbbrevInitial, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
if(P->Abbreviations[AS_GIVEN_OR_NICKNAME].Length > 0)
{
Print(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair AbbrevGivenOrNickname = { .Key = IDENT_ABBREV_GIVEN_OR_NICKNAME, .String = P->Abbreviations[AS_GIVEN_OR_NICKNAME], .Type = PT_STRING }; PrintPair(&AbbrevGivenOrNickname, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
if(P->Abbreviations[AS_DOTTED_INITIAL_AND_SURNAME].Length > 0)
{
Print(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair AbbrevDottedInitialAndSurname = { .Key = IDENT_ABBREV_DOTTED_INITIAL_AND_SURNAME, .String = P->Abbreviations[AS_DOTTED_INITIAL_AND_SURNAME], .Type = PT_STRING }; PrintPair(&AbbrevDottedInitialAndSurname, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
if(P->Homepage.Length > 0)
{
Print(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair Homepage = { .Key = IDENT_HOMEPAGE, .String = P->Homepage, .Type = PT_STRING }; PrintPair(&Homepage, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
for(int i = 0; i < P->Support.ItemCount; ++i)
{
PrintSupport(GetPlaceInBook(&P->Support, i), Typography, IndentationLevel, &RowsRequired);
}
}
uint8_t
GetRowsRequiredForRoleInfo(role *R)
{
uint8_t RowsRequired = 0;
if(R->Name.Length > 0) { ++RowsRequired; }
if(R->Plural.Length > 0) { ++RowsRequired; }
int NonSpeakingLines = 1;
RowsRequired += NonSpeakingLines;
return RowsRequired;
}
void
PrintRole(role *R, typography *Typography)
{
bool HaveInfo = R->Name.Length > 0 || R->Plural.Length > 0;
Print(stderr, "\n"
"%s%s%s%s%s ", HaveInfo ? Typography->UpperLeftCorner : Typography->UpperLeft,
Typography->Horizontal, Typography->Horizontal, Typography->Horizontal, Typography->UpperRight);
PrintStringC(CS_GREEN_BOLD, R->ID);
Print(stderr, "\n");
uint8_t RowsRequired = GetRowsRequiredForRoleInfo(R);
int IndentationLevel = 0;
bool ShouldFillSyntax = FALSE;
if(R->Name.Length > 0)
{
Print(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair Name = { .Key = IDENT_NAME, .String = R->Name, .Type = PT_STRING }; PrintPair(&Name, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
if(R->Plural.Length > 0)
{
Print(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair Plural = { .Key = IDENT_PLURAL, .String = R->Plural, .Type = PT_STRING }; PrintPair(&Plural, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--RowsRequired;
}
Print(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
config_pair NonSpeaking = { .Key = IDENT_NON_SPEAKING, .bool = R->NonSpeaking, .Type = PT_BOOL }; PrintPair(&NonSpeaking, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
--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) { Print(stderr, "\n"); }
}
void
ExtendLineageInBook(memory_book *M, string Lineage, bool AppendNewline)
{
string Parent = StripComponentFromPath(Lineage);
ExtendStringCInBook(M, CS_BLACK_BOLD, Parent);
if(Parent.Length > 0)
{
ExtendStringCInBook(M, CS_BLACK_BOLD, Wrap0("/"));
}
string Us = GetFinalComponent(Lineage);
ExtendStringCInBook(M, CS_BLUE_BOLD, Us);
if(AppendNewline) { ExtendStringInBook(M, Wrap0("\n")); }
}
uint8_t
GetColumnsRequiredForMedium(medium *M, typography *T)
{
uint8_t Result = M->ID.Length + StringLength(T->Delimiter) + M->Name.Length + sizeof(" (")-1 + M->Icon.Length + sizeof(")")-1;
if(M->Hidden) { Result += sizeof(" [hidden]")-1; }
return Result;
}
void
PrintTitle(char *Text, typography *T, uint8_t Generation, int AvailableColumns,
int64_t SectionItemCount /* NOTE(matt): Use any non-0 when the section is known to have content */)
{
char *LeftmostChar = "";
char *InnerChar = "";
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;
Print(stderr, "%s", LeftmostChar);
for(int i = 0; i < LeftCharsRequired; ++i)
{
Print(stderr, "%s", InnerChar);
}
Print(stderr, " %s ", Text);
for(int i = 0; i < RightCharsRequired; ++i)
{
Print(stderr, "%s", InnerChar);
}
Print(stderr, "%s", RightmostChar);
if(SectionItemCount == 0)
{
//Print(stderr, "\n");
CarriageReturn(T, Generation);
}
}
void
PrintMedia(project *P, typography *T, uint8_t Generation, int AvailableColumns)
{
int IndentationLevel = 0;
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
PrintTitle("Media", T, Generation, AvailableColumns, P->Medium.ItemCount);
CarriageReturn(T, Generation);
Print(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)
{
Print(stderr, " %s", T->Separator);
RunningAvailableColumns -= StringLengthOfSpacePlusSeparator;
if(ColumnsRequired + 1 <= RunningAvailableColumns)
{
Print(stderr, " ");
--RunningAvailableColumns;
PrintMedium(T, This, T->Delimiter, IndentationLevel, FALSE);
}
else
{
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
RunningAvailableColumns = AvailableColumns;
PrintMedium(T, This, T->Delimiter, IndentationLevel, FALSE);
}
}
else
{
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
RunningAvailableColumns = AvailableColumns;
Print(stderr, "%s ", T->Separator);
RunningAvailableColumns -= StringLengthOfSpacePlusSeparator;
PrintMedium(T, This, T->Delimiter, IndentationLevel, FALSE);
}
RunningAvailableColumns -= ColumnsRequired;
}
}
void
PrintCredits(config *C, project *P, typography *T, uint8_t Generation, int IndentationLevel, int AvailableColumns)
{
for(int i = 0; i < C->Role.ItemCount; ++i)
{
role *Role = GetPlaceInBook(&C->Role, i);
int CreditCount = 0;
for(int j = 0; j < P->Credit.ItemCount; ++j)
{
credit *Credit = GetPlaceInBook(&P->Credit, j);
if(Role == Credit->Role)
{
++CreditCount;
}
}
if(CreditCount > 0)
{
CarriageReturn(T, Generation);
int Alignment = 0;
Alignment += Print(stderr, "%s", T->Margin);
if(CreditCount == 1)
{
Alignment += PrintStringC(CS_YELLOW_BOLD, Role->Name.Length ? Role->Name : Role->ID);
}
else
{
if(Role->Plural.Length > 0)
{
Alignment += PrintStringC(CS_YELLOW_BOLD, Role->Plural);
}
else
{
Alignment += PrintStringC(CS_YELLOW_BOLD, Role->Name.Length ? Role->Name : Role->ID);
Alignment += PrintStringC(CS_YELLOW_BOLD, Wrap0("s"));
}
}
Alignment += Print(stderr, "%s", T->Delimiter);
bool Printed = FALSE;
for(int j = 0; j < P->Credit.ItemCount; ++j)
{
credit *Credit = GetPlaceInBook(&P->Credit, j);
if(Role == Credit->Role)
{
if(Printed)
{
CarriageReturn(T, Generation);
AlignText(Alignment);
}
PrintStringC(CS_GREEN_BOLD, Credit->Person->Name.Length ? Credit->Person->Name : Credit->Person->ID);
Printed = TRUE;
}
}
}
}
}
void
TypesetPair(typography *T, uint8_t Generation, config_identifier_id Key, string Value, int AvailableColumns)
{
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
int CharactersInKeyPlusDelimiter = StringLength(ConfigIdentifiers[Key].String) + StringLength(T->Delimiter);
int AvailableColumnsForValue = AvailableColumns - CharactersInKeyPlusDelimiter;
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[Key].String);
Print(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);
Print(stderr, "%s", T->Margin);
for(int i = 0; i < CharactersInKeyPlusDelimiter; ++i)
{
Print(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
TypesetNumber(typography *T, uint8_t Generation, config_identifier_id Key, int64_t Value)
{
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
config_pair Pair = { .Key = Key, .int64_t = Value, .Type = PT_INT64 };
bool ShouldFillSyntax = FALSE;
PrintPair(&Pair, T->Delimiter, ShouldFillSyntax, 0, FALSE, FALSE);
}
void
TypesetBool(typography *T, uint8_t Generation, config_identifier_id Key, bool Value)
{
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
config_pair Pair = { .Key = Key, .bool = Value, .Type = PT_BOOL };
bool ShouldFillSyntax = FALSE;
PrintPair(&Pair, T->Delimiter, ShouldFillSyntax, 0, FALSE, FALSE);
}
void
TypesetGenre(typography *T, uint8_t Generation, genre G)
{
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_GENRE].String);
Print(stderr, "%s", T->Delimiter);
PrintC(CS_GREEN_BOLD, GenreStrings[G]);
}
void
TypesetVODPlatform(typography *T, uint8_t Generation, vod_platform P)
{
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_VOD_PLATFORM].String);
Print(stderr, "%s", T->Delimiter);
if(IsValidVODPlatform(P))
{
PrintC(CS_GREEN_BOLD, VODPlatformStrings[P]);
}
else
{
PrintC(CS_YELLOW, "[unset]");
}
}
void
TypesetNumberingMethod(typography *T, uint8_t Generation, numbering_method N)
{
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_NUMBERING_METHOD].String);
Print(stderr, "%s", T-> Delimiter);
PrintC(CS_GREEN_BOLD, NumberingMethodStrings[N]);
}
void
TypesetNumberingScheme(typography *T, uint8_t Generation, numbering_scheme N)
{
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_NUMBERING_SCHEME].String);
Print(stderr, "%s", T-> Delimiter);
PrintC(CS_GREEN_BOLD, NumberingSchemeStrings[N]);
}
void
PrintProject(config *C, project *P, typography *T, int Ancestors, int IndentationLevel, int TerminalColumns)
{
int Generation = Ancestors + 1;
CarriageReturn(T, Ancestors);
Print(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);
Print(stderr, "%s", T->Margin);
PrintTitle("Settings", T, Generation, AvailableColumns, -1);
TypesetPair(T, Generation, IDENT_TITLE, P->Title, AvailableColumns);
TypesetPair(T, Generation, IDENT_HTML_TITLE, P->HTMLTitle, AvailableColumns);
TypesetPair(T, Generation, IDENT_DEFAULT_CC_LANG, P->DefaultCCLang.Length ? P->DefaultCCLang : EmptyString(), AvailableColumns);
TypesetPair(T, Generation, IDENT_DEFAULT_MEDIUM, P->DefaultMedium ? P->DefaultMedium->ID : EmptyString(), 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_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);
TypesetNumberingMethod(T, Generation, P->Numbering.Method);
TypesetNumberingScheme(T, Generation, P->Numbering.Scheme);
TypesetPair(T, Generation, IDENT_NUMBERING_UNIT, P->Numbering.Unit, AvailableColumns);
TypesetPair(T, Generation, IDENT_NUMBERING_FILENAME_PREFIX, P->Numbering.FilenameDerived.Prefix, AvailableColumns);
TypesetNumber(T, Generation, IDENT_NUMBERING_START, P->Numbering.Auto.StartingNumber);
TypesetBool(T, Generation, IDENT_NUMBERING_ZERO_PAD, P->Numbering.Auto.ZeroPadded);
CarriageReturn(T, Generation);
TypesetGenre(T, Generation, P->Genre);
TypesetVODPlatform(T, Generation, P->VODPlatform);
TypesetBool(T, Generation, IDENT_IGNORE_PRIVACY, P->IgnorePrivacy);
TypesetBool(T, Generation, IDENT_SINGLE_BROWSER_TAB, P->SingleBrowserTab);
TypesetPair(T, Generation, IDENT_OWNER, P->Owner ? P->Owner->ID : Wrap0(""), AvailableColumns);
CarriageReturn(T, Generation);
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
PrintTitle("Credits", T, Generation, AvailableColumns, P->Credit.ItemCount);
if(P->Credit.ItemCount > 0)
{
PrintCredits(C, P, T, Generation, IndentationLevel, AvailableColumns);
}
else
{
Print(stderr, "%s", T->Margin);
PrintC(CS_YELLOW, "[none]");
}
if(P->Child.ItemCount)
{
CarriageReturn(T, Generation);
CarriageReturn(T, Generation);
Print(stderr, "%s", T->Margin);
PrintTitle("Children", T, Generation, AvailableColumns, -1);
++Ancestors;
for(int i = 0; i < P->Child.ItemCount; ++i)
{
PrintProject(C, GetPlaceInBook(&P->Child, i), T, Ancestors, IndentationLevel, TerminalColumns);
}
--Ancestors;
}
CarriageReturn(T, Ancestors);
Print(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
Print(stderr, "\n");
PrintTitle("Global Settings", &Typography, 0, TermCols, -1);
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);
TypesetPair(&Typography, 0, IDENT_INSTANCE_TITLE, C->InstanceTitle, AvailableColumns);
Print(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);
Print(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);
Print(stderr, "\n"
"\n"
"%s", Typography.Margin);
bool ShouldFillSyntax = FALSE;
PrintBool("Respecting Privacy (derived from projects)", C->RespectingPrivacy, Typography.Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
Print(stderr, "%s", Typography.Margin);
int SecondsPerMinute = 60;
config_pair PrivacyCheckInterval = { .Key = IDENT_PRIVACY_CHECK_INTERVAL, .int64_t = C->PrivacyCheckInterval / SecondsPerMinute, .Type = PT_INT64 }; PrintPair(&PrivacyCheckInterval, Typography.Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
Print(stderr, "%s", Typography.Margin);
PrintLogLevel(ConfigIdentifiers[IDENT_LOG_LEVEL].String, C->LogLevel, Typography.Delimiter, IndentationLevel, TRUE);
Print(stderr, "\n");
PrintTitle("People", &Typography, 0, TermCols, C->Person.ItemCount);
for(int i = 0; i < C->Person.ItemCount; ++i)
{
PrintPerson(GetPlaceInBook(&C->Person, i), &Typography);
}
Print(stderr, "\n");
PrintTitle("Roles", &Typography, 0, TermCols, C->Role.ItemCount);
for(int i = 0; i < C->Role.ItemCount; ++i)
{
PrintRole(GetPlaceInBook(&C->Role, i), &Typography);
}
Print(stderr, "\n");
PrintTitle("Projects", &Typography, 0, TermCols, -1);
for(int i = 0; i < C->Project.ItemCount; ++i)
{
PrintProject(C, GetPlaceInBook(&C->Project, i), &Typography, 0, IndentationLevel, TermCols);
}
}
}
void
FreeProject(project *P)
{
FreeBook(&P->Medium);
FreeBook(&P->Credit);
for(int i = 0; i < P->Child.ItemCount; ++i)
{
FreeProject(GetPlaceInBook(&P->Child, i));
}
FreeBook(&P->Child);
FreeTemplate(&P->SearchTemplate);
FreeTemplate(&P->PlayerTemplate);
}
// NOTE(matt): Making this a #define permits Free() to null out the incoming C
#define FreeConfig(C)\
{\
if(C)\
{\
for(int i = 0; i < C->Person.ItemCount; ++i)\
{\
person *Person = GetPlaceInBook(&C->Person, i);\
FreeBook(&Person->Support);\
}\
FreeBook(&C->Person);\
FreeBook(&C->Role);\
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_verifiers
InitVerifiers(void)
{
config_verifiers Result = {};
Result.VariantStrings = InitBook(sizeof(variant_string), 8);
Result.HMMLDirs.ID0 = IDENT_HMML_DIR;
Result.HMMLDirs.Associations = InitBook(sizeof(config_string_association), 8);
Result.BaseDirAndSearchLocation.ID0 = IDENT_BASE_DIR;
Result.BaseDirAndSearchLocation.ID1 = IDENT_SEARCH_LOCATION;
Result.BaseDirAndSearchLocation.Associations = InitBook(sizeof(config_string_association), 8);
Result.BaseDirAndPlayerLocation.ID0 = IDENT_BASE_DIR;
Result.BaseDirAndPlayerLocation.ID1 = IDENT_PLAYER_LOCATION;
Result.BaseDirAndPlayerLocation.Associations = InitBook(sizeof(config_string_association), 8);
return Result;
}
bool
IsPositioned(config_pair *Position)
{
return Position && Position->int64_t != 0;
}
void
PositionRolesInConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *S)
{
memory_book RolesStaging = InitBookOfPointers(4);
for(int i = 0; i < S->Trees.ItemCount; ++i)
{
scope_tree *Tree = GetPlaceInBook(&S->Trees, i);
if(Tree->ID.Key == IDENT_ROLE)
{
scope_tree **This = MakeSpaceInBook(&RolesStaging);
*This = Tree;
}
}
int SlotCount = RolesStaging.ItemCount;
scope_tree *RoleSlot[SlotCount];
Clear(RoleSlot, sizeof(scope_tree *) * SlotCount);
for(int i = 0; i < SlotCount; ++i)
{
scope_tree **Role = GetPlaceInBook(&RolesStaging, i);
config_pair *Position = GetPair(*Role, IDENT_POSITION);
if(IsPositioned(Position))
{
int TargetPos = 0;
if(Position->int64_t < 0)
{
TargetPos = SlotCount + Position->int64_t;
Clamp(0, TargetPos, SlotCount - 1);
while(RoleSlot[TargetPos] && TargetPos > 0)
{
--TargetPos;
}
while(RoleSlot[TargetPos] && TargetPos < SlotCount - 1)
{
++TargetPos;
}
}
else if(Position->int64_t > 0)
{
TargetPos = Position->int64_t - 1;
Clamp(0, TargetPos, SlotCount - 1);
while(RoleSlot[TargetPos] && TargetPos < SlotCount - 1)
{
++TargetPos;
}
while(RoleSlot[TargetPos] && TargetPos > 0)
{
--TargetPos;
}
}
RoleSlot[TargetPos] = *Role;
}
}
for(int i = 0; i < SlotCount; ++i)
{
scope_tree **Role = GetPlaceInBook(&RolesStaging, i);
config_pair *Position = GetPair(*Role, IDENT_POSITION);
if(!IsPositioned(Position))
{
for(int j = 0; j < SlotCount; ++j)
{
if(!RoleSlot[j])
{
RoleSlot[j] = *Role;
break;
}
}
}
}
for(int i = 0; i < SlotCount; ++i)
{
PushRoleOntoConfig(C, E, V, RoleSlot[i]);
}
FreeBook(&RolesStaging);
}
config *
ResolveVariables(scope_tree *S)
{
Assert(LOG_COUNT == ArrayCount(LogLevelStrings));
config *Result = calloc(1, sizeof(config));
Result->ResolvedVariables = InitBookOfStrings(Kilobytes(1));
Result->Person = InitBook(sizeof(person), 8);
Result->Role = InitBook(sizeof(role), 4);
Result->Project = InitBook(sizeof(project), 8);
resolution_errors Errors = {};
Errors.Warnings = InitBook(sizeof(resolution_error), 16);
Errors.Errors = InitBook(sizeof(resolution_error), 16);
config_verifiers Verifiers = InitVerifiers();
for(int i = 0; i < S->Pairs.ItemCount; ++i)
{
config_pair *Pair = GetPlaceInBook(&S->Pairs, i);
string Filepath = Wrap0(Pair->Position.Filename);
switch(Pair->Key)
{
// NOTE(matt): String
case IDENT_DB_LOCATION:
{ Result->DatabaseLocation = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS); } break;
case IDENT_INSTANCE_TITLE:
{ Result->InstanceTitle = ResolveString(Result, &Errors, S, Pair, TRUE); } break;
case IDENT_GLOBAL_SEARCH_DIR:
{
Result->GlobalSearchDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS);
if(Result->GlobalSearchDir.Length > MAX_BASE_DIR_LENGTH)
{
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
&Result->GlobalSearchDir, MAX_BASE_DIR_LENGTH);
PushError(&Errors, S_ERROR, 0, Pair->Key);
}
} break;
case IDENT_GLOBAL_SEARCH_URL:
{
Result->GlobalSearchURL = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS);
if(Result->GlobalSearchURL.Length > MAX_BASE_URL_LENGTH)
{
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
&Result->GlobalSearchURL, MAX_BASE_URL_LENGTH);
PushError(&Errors, S_ERROR, 0, Pair->Key);
}
} break;
case IDENT_GLOBAL_TEMPLATES_DIR:
{ Result->GlobalTemplatesDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS); } break;
case IDENT_GLOBAL_SEARCH_TEMPLATE:
{ Result->GlobalSearchTemplatePath = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL); } break;
case IDENT_GLOBAL_THEME:
{
Result->GlobalTheme = ResolveString(Result, &Errors, S, Pair, FALSE);
if(Result->GlobalTheme.Length > MAX_THEME_LENGTH)
{
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
&Result->GlobalTheme, MAX_THEME_LENGTH);
PushError(&Errors, S_ERROR, 0, Pair->Key);
}
} break;
case IDENT_ASSETS_ROOT_DIR:
{
Result->AssetsRootDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS);
if(Result->AssetsRootDir.Length > MAX_ROOT_DIR_LENGTH)
{
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
&Result->AssetsRootDir, MAX_ROOT_DIR_LENGTH);
PushError(&Errors, S_ERROR, 0, Pair->Key);
}
} break;
case IDENT_ASSETS_ROOT_URL:
{
Result->AssetsRootURL = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS);
if(Result->AssetsRootURL.Length > MAX_ROOT_URL_LENGTH)
{
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
&Result->AssetsRootURL, MAX_ROOT_URL_LENGTH);
PushError(&Errors, S_ERROR, 0, Pair->Key);
}
} break;
case IDENT_CSS_PATH:
{
Result->CSSDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL);
if(Result->CSSDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH)
{
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
&Result->CSSDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH);
PushError(&Errors, S_ERROR, 0, Pair->Key);
}
} break;
case IDENT_IMAGES_PATH:
{
Result->ImagesDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL);
if(Result->ImagesDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH)
{
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
&Result->ImagesDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH);
PushError(&Errors, S_ERROR, 0, Pair->Key);
}
} break;
case IDENT_JS_PATH:
{
Result->JSDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL);
if(Result->JSDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH)
{
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
&Result->JSDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH);
PushError(&Errors, S_ERROR, 0, Pair->Key);
}
} break;
case IDENT_QUERY_STRING:
{ Result->QueryString = ResolveString(Result, &Errors, S, Pair, FALSE); } break;
case IDENT_CACHE_DIR:
{ Result->CacheDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS); } break;
// NOTE(matt): int64_t
case IDENT_PRIVACY_CHECK_INTERVAL:
{ Result->PrivacyCheckInterval = SECONDS_PER_MINUTE * Pair->int64_t; } break;
case IDENT_LOG_LEVEL:
{ Result->LogLevel = Pair->int64_t; } break;
// NOTE(matt): bool
case IDENT_SUPPRESS_PROMPTS:
{ Result->SuppressingPrompts = Pair->bool; } break;
default: break;
}
}
for(int i = 0; i < S->Trees.ItemCount; ++i)
{
scope_tree *Tree = GetPlaceInBook(&S->Trees, i);
if(Tree->ID.Key == IDENT_PERSON)
{
PushPersonOntoConfig(Result, &Errors, &Verifiers, Tree);
}
}
PositionRolesInConfig(Result, &Errors, &Verifiers, S);
for(int i = 0; i < S->Trees.ItemCount; ++i)
{
scope_tree *Tree = GetPlaceInBook(&S->Trees, i);
if(Tree->ID.Key == IDENT_PROJECT)
{
PushProjectOntoConfig(Result, &Errors, &Verifiers, Tree);
}
}
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.Errors.ItemCount > 0)
{
PrintAssociationClashes(&Verifiers.HMMLDirs);
PrintAssociationClashes(&Verifiers.BaseDirAndSearchLocation);
PrintAssociationClashes(&Verifiers.BaseDirAndPlayerLocation);
PrintVariantInconsistencies(&Verifiers.VariantStrings);
FreeConfig(Result);
}
FreeVerifiers(&Verifiers);
FreeErrors(&Errors);
return Result;
}
config *
ParseConfig(string Path, memory_book *TokensList)
{
//PrintFunctionName("ParseConfig()");
_memory_book(config_type_spec) TypeSpecs = InitTypeSpecs();
#if 0
MEM_LOOP_PRE_FREE("InitTypeSpecs")
FreeTypeSpecs(&TypeSpecs);
MEM_LOOP_PRE_WORK()
TypeSpecs = InitTypeSpecs();
MEM_LOOP_POST("InitTypeSpecs")
#endif
config *Result = 0;
tokens *T = Tokenise(TokensList, Path, NA, NA);
#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 = InitRootScopeTree();
SetTypeSpec(ScopeTree, &TypeSpecs);
SetDefaults(ScopeTree, &TypeSpecs);
ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, NA);
// TODO(matt): Mem testing
//
#if 0
MEM_LOOP_PRE_FREE("ScopeTree")
FreeScopeTree(ScopeTree);
FreeTokensList(TokensList);
MEM_LOOP_PRE_WORK()
T = Tokenise(TokensList, Path);
ScopeTree = InitRootScopeTree();
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)
{
//Print(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