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