4960 lines
170 KiB
C
4960 lines
170 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 = MakeSpaceInBook(&Set->Token);
|
|
This->Type = Token->Type;
|
|
This->LineNumber = Set->CurrentLine;
|
|
|
|
if(Token->Type == TOKEN_NUMBER)
|
|
{
|
|
This->int64_t = StringToInt(Token->Content);
|
|
}
|
|
else
|
|
{
|
|
This->Content = Token->Content;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
PrintToken(token *T, uint64_t Index)
|
|
{
|
|
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)
|
|
{
|
|
PrintToken(GetPlaceInBook(&T->Token, i), i);
|
|
}
|
|
}
|
|
|
|
tokens *
|
|
PushTokens(memory_book *TokensList)
|
|
{
|
|
tokens *This = MakeSpaceInBook(TokensList);
|
|
This->Token = InitBook(sizeof(token), 16);
|
|
This->CurrentLine = 1;
|
|
return This;
|
|
}
|
|
|
|
void
|
|
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;
|
|
// TODO(matt): string SortName;
|
|
string QuoteUsername;
|
|
string Homepage;
|
|
_memory_book(support) Support;
|
|
} person;
|
|
|
|
typedef struct
|
|
{
|
|
string ID;
|
|
string Name;
|
|
string Plural;
|
|
bool NonSpeaking;
|
|
} role;
|
|
|
|
typedef struct
|
|
{
|
|
person *Person;
|
|
role *Role;
|
|
} credit;
|
|
|
|
typedef struct project
|
|
{
|
|
string ID;
|
|
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;
|
|
|
|
vod_platform VODPlatform;
|
|
|
|
bool DenyBespokeTemplates;
|
|
bool SingleBrowserTab;
|
|
bool IgnorePrivacy;
|
|
|
|
person *Owner;
|
|
_memory_book(credit *) Credit;
|
|
|
|
_memory_book(medium) Medium;
|
|
medium *DefaultMedium;
|
|
|
|
_memory_book(project) Child;
|
|
|
|
struct project *Parent;
|
|
|
|
genre Genre;
|
|
numbering_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;
|
|
bool SuppressingPrompts;
|
|
time_t PrivacyCheckInterval;
|
|
uint8_t LogLevel;
|
|
|
|
_memory_book(person) Person;
|
|
_memory_book(role) Role;
|
|
_memory_book(project) Project;
|
|
memory_book ResolvedVariables;
|
|
} config;
|
|
|
|
char *ExpandPath(string Path, string *RelativeToFile); // NOTE(matt): Forward declared. Consider reorganising the code?
|
|
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 };
|
|
string Filepath = Wrap0(Result->File.Path);
|
|
ConfigError(&Filepath, Result->CurrentLine, S_WARNING, "Mismatched closing multiline comment marker: ", &Char);
|
|
}
|
|
else if(!StringsDifferS(TokenStrings[TOKEN_DOUBLEQUOTE], B))
|
|
{
|
|
T.Type = TOKEN_STRING;
|
|
++B->Ptr;
|
|
T.Content.Base = B->Ptr;
|
|
while(B->Ptr - B->Location < B->Size && *B->Ptr != '"')
|
|
{
|
|
if(*B->Ptr == '\\')
|
|
{
|
|
++T.Content.Length;
|
|
++B->Ptr;
|
|
}
|
|
++T.Content.Length;
|
|
++B->Ptr;
|
|
}
|
|
Advancement = 1;
|
|
}
|
|
else if(!StringsDifferS(TokenStrings[TOKEN_MINUS], B))
|
|
{
|
|
T.Type = TOKEN_MINUS;
|
|
T.Content.Base = B->Ptr;
|
|
++T.Content.Length;
|
|
Advancement = 1;
|
|
}
|
|
else if(!StringsDifferS(TokenStrings[TOKEN_ASSIGN], B))
|
|
{
|
|
T.Type = TOKEN_ASSIGN;
|
|
T.Content.Base = B->Ptr;
|
|
++T.Content.Length;
|
|
Advancement = 1;
|
|
}
|
|
else if(!StringsDifferS(TokenStrings[TOKEN_OPEN_BRACE], B))
|
|
{
|
|
T.Type = TOKEN_OPEN_BRACE;
|
|
T.Content.Base = B->Ptr;
|
|
++T.Content.Length;
|
|
Advancement = 1;
|
|
}
|
|
else if(!StringsDifferS(TokenStrings[TOKEN_CLOSE_BRACE], B))
|
|
{
|
|
T.Type = TOKEN_CLOSE_BRACE;
|
|
T.Content.Base = B->Ptr;
|
|
++T.Content.Length;
|
|
Advancement = 1;
|
|
}
|
|
else if(!StringsDifferS(TokenStrings[TOKEN_SEMICOLON], B))
|
|
{
|
|
T.Type = TOKEN_SEMICOLON;
|
|
T.Content.Base = B->Ptr;
|
|
++T.Content.Length;
|
|
Advancement = 1;
|
|
}
|
|
else if(IsValidIdentifierCharacter(*B->Ptr))
|
|
{
|
|
T.Type = TOKEN_IDENTIFIER;
|
|
T.Content.Base = B->Ptr;
|
|
while(IsValidIdentifierCharacter(*B->Ptr))
|
|
{
|
|
++T.Content.Length;
|
|
++B->Ptr;
|
|
}
|
|
Advancement = 0;
|
|
}
|
|
else if(IsNumber(*B->Ptr))
|
|
{
|
|
T.Type = TOKEN_NUMBER;
|
|
T.Content.Base = B->Ptr;
|
|
while(IsNumber(*B->Ptr))
|
|
{
|
|
++T.Content.Length;
|
|
++B->Ptr;
|
|
}
|
|
Advancement = 0;
|
|
}
|
|
else if(*B->Ptr == '\n')
|
|
{
|
|
T.Type = TOKEN_NEWLINE;
|
|
T.Content.Base = B->Ptr;
|
|
T.Content.Length = 1;
|
|
++Result->CurrentLine;
|
|
Advancement = 1;
|
|
}
|
|
else
|
|
{
|
|
T.Type = TOKEN_NULL;
|
|
string Char = GetUTF8Character(B->Ptr, B->Size - (B->Ptr - B->Location));
|
|
Advancement = Char.Length;
|
|
string Filepath = Wrap0(Result->File.Path);
|
|
if(Char.Base)
|
|
{
|
|
ConfigError(&Filepath, Result->CurrentLine, S_WARNING, "Unhandled character (ignored): ", &Char);
|
|
}
|
|
else
|
|
{
|
|
ConfigErrorInt(&Filepath, Result->CurrentLine, S_WARNING, "Malformed UTF-8 bytes encountered (skipped): ", Char.Length);
|
|
}
|
|
}
|
|
|
|
PushToken(Result, &T);
|
|
Advance(B, Advancement);
|
|
SkipWhitespace(Result, B);
|
|
}
|
|
}
|
|
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(Type == This->Type);
|
|
}
|
|
|
|
bool
|
|
ExpectToken(tokens *T, token_type Type, token *Dest)
|
|
{
|
|
bool Result = FALSE;
|
|
|
|
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
|
|
if(Type == This->Type)
|
|
{
|
|
Result = TRUE;
|
|
if(Dest)
|
|
{
|
|
*Dest = *This;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Dest = 0;
|
|
ConfigErrorExpectation(T, Type, 0);
|
|
}
|
|
++T->CurrentIndex;
|
|
return Result;
|
|
}
|
|
|
|
char *FieldTypeNames[] =
|
|
{
|
|
"bare",
|
|
"boolean",
|
|
"number",
|
|
"scope",
|
|
"string",
|
|
};
|
|
|
|
typedef enum
|
|
{
|
|
FT_BARE,
|
|
FT_BOOLEAN,
|
|
FT_NUMBER,
|
|
FT_SCOPE,
|
|
FT_STRING,
|
|
} config_field_type;
|
|
|
|
typedef struct
|
|
{
|
|
config_identifier_id ID;
|
|
config_field_type Type;
|
|
bool Singleton;
|
|
bool IsSetLocally;
|
|
} config_type_field;
|
|
|
|
typedef struct
|
|
{
|
|
config_identifier_id ID;
|
|
bool Permeable;
|
|
_memory_book(config_type_field) Field;
|
|
} config_type_spec;
|
|
|
|
config_type_spec *
|
|
PushTypeSpec(memory_book *TypeSpecs, config_identifier_id ID, bool IsPermeable)
|
|
{
|
|
//PrintFunctionName("PushTypeSpec()");
|
|
config_type_spec *Result = MakeSpaceInBook(TypeSpecs);
|
|
Result->ID = ID;
|
|
Result->Field = InitBook(sizeof(config_type_field), 4);
|
|
Result->Permeable = IsPermeable;
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
PushTypeSpecField(config_type_spec *Spec, config_field_type Type, config_identifier_id ID, bool Singleton)
|
|
{
|
|
//PrintFunctionName("PushTypeSpecField()");
|
|
config_type_field *This = MakeSpaceInBook(&Spec->Field);
|
|
This->Type = Type;
|
|
This->ID = ID;
|
|
This->Singleton = Singleton;
|
|
This->IsSetLocally = FALSE;
|
|
}
|
|
|
|
_memory_book(config_type_specs)
|
|
InitTypeSpecs(void)
|
|
{
|
|
//PrintFunctionName("InitTypeSpecs()");
|
|
memory_book Result = InitBook(sizeof(config_type_spec), 16);
|
|
|
|
config_type_spec *Root = PushTypeSpec(&Result, IDENT_NULL, FALSE);
|
|
PushTypeSpecField(Root, FT_STRING, IDENT_ASSETS_ROOT_DIR, TRUE);
|
|
PushTypeSpecField(Root, FT_STRING, IDENT_ASSETS_ROOT_URL, TRUE);
|
|
PushTypeSpecField(Root, FT_STRING, IDENT_BASE_DIR, TRUE);
|
|
PushTypeSpecField(Root, FT_STRING, IDENT_BASE_URL, TRUE);
|
|
PushTypeSpecField(Root, FT_STRING, IDENT_CACHE_DIR, TRUE);
|
|
PushTypeSpecField(Root, FT_STRING, IDENT_COHOST, FALSE);
|
|
PushTypeSpecField(Root, FT_STRING, IDENT_CSS_PATH, TRUE);
|
|
PushTypeSpecField(Root, FT_STRING, IDENT_DB_LOCATION, TRUE);
|
|
PushTypeSpecField(Root, FT_STRING, IDENT_DEFAULT_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_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_BOOLEAN, IDENT_SUPPRESS_PROMPTS, TRUE);
|
|
PushTypeSpecField(Root, FT_NUMBER, IDENT_PRIVACY_CHECK_INTERVAL, TRUE);
|
|
PushTypeSpecField(Root, FT_SCOPE, IDENT_CREDIT, FALSE);
|
|
PushTypeSpecField(Root, FT_SCOPE, IDENT_INCLUDE, FALSE);
|
|
PushTypeSpecField(Root, FT_SCOPE, IDENT_MEDIUM, FALSE);
|
|
PushTypeSpecField(Root, FT_SCOPE, IDENT_PERSON, FALSE);
|
|
PushTypeSpecField(Root, FT_SCOPE, IDENT_ROLE, FALSE);
|
|
PushTypeSpecField(Root, FT_SCOPE, IDENT_PROJECT, FALSE);
|
|
PushTypeSpecField(Root, FT_SCOPE, IDENT_SUPPORT, FALSE);
|
|
|
|
config_type_spec *Support = PushTypeSpec(&Result, IDENT_SUPPORT, FALSE);
|
|
PushTypeSpecField(Support, FT_STRING, IDENT_ICON, TRUE);
|
|
PushTypeSpecField(Support, FT_STRING, IDENT_ICON_NORMAL, TRUE);
|
|
PushTypeSpecField(Support, FT_STRING, IDENT_ICON_FOCUSED, TRUE);
|
|
PushTypeSpecField(Support, FT_STRING, IDENT_ICON_TYPE, TRUE);
|
|
PushTypeSpecField(Support, FT_STRING, IDENT_ICON_VARIANTS, TRUE);
|
|
PushTypeSpecField(Support, FT_STRING, IDENT_URL, TRUE);
|
|
|
|
config_type_spec *Include = PushTypeSpec(&Result, IDENT_INCLUDE, FALSE);
|
|
PushTypeSpecField(Include, FT_STRING, IDENT_ALLOW, FALSE);
|
|
PushTypeSpecField(Include, FT_STRING, IDENT_DENY, FALSE);
|
|
|
|
config_type_spec *Person = PushTypeSpec(&Result, IDENT_PERSON, FALSE);
|
|
PushTypeSpecField(Person, FT_STRING, IDENT_NAME, TRUE);
|
|
PushTypeSpecField(Person, FT_STRING, IDENT_HOMEPAGE, TRUE);
|
|
PushTypeSpecField(Person, FT_STRING, IDENT_QUOTE_USERNAME, TRUE);
|
|
PushTypeSpecField(Person, FT_SCOPE, IDENT_SUPPORT, FALSE);
|
|
|
|
config_type_spec *Role = PushTypeSpec(&Result, IDENT_ROLE, FALSE);
|
|
PushTypeSpecField(Role, FT_STRING, IDENT_NAME, TRUE);
|
|
PushTypeSpecField(Role, FT_STRING, IDENT_PLURAL, TRUE);
|
|
PushTypeSpecField(Role, FT_NUMBER, IDENT_POSITION, TRUE);
|
|
PushTypeSpecField(Role, FT_BOOLEAN, IDENT_NON_SPEAKING, TRUE);
|
|
|
|
config_type_spec *Credit = PushTypeSpec(&Result, IDENT_CREDIT, FALSE);
|
|
PushTypeSpecField(Credit, FT_STRING, IDENT_ROLE, FALSE);
|
|
|
|
config_type_spec *Medium = PushTypeSpec(&Result, IDENT_MEDIUM, FALSE);
|
|
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON, TRUE);
|
|
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_NORMAL, TRUE);
|
|
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_FOCUSED, TRUE);
|
|
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_DISABLED, TRUE);
|
|
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_TYPE, TRUE);
|
|
PushTypeSpecField(Medium, FT_STRING, IDENT_ICON_VARIANTS, TRUE);
|
|
PushTypeSpecField(Medium, FT_STRING, IDENT_NAME, TRUE);
|
|
PushTypeSpecField(Medium, FT_BOOLEAN, IDENT_HIDDEN, TRUE);
|
|
|
|
config_type_spec *Project = PushTypeSpec(&Result, IDENT_PROJECT, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_BASE_DIR, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_BASE_URL, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_DEFAULT_MEDIUM, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_GENRE, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_HMML_DIR, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_SCHEME, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_OWNER, TRUE); // NOTE(matt): Do not remove, because ResolveLocalVariable() recognises it
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_PLAYER_LOCATION, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_PLAYER_TEMPLATE, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_QUERY_STRING, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_SEARCH_LOCATION, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_SEARCH_TEMPLATE, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_TEMPLATES_DIR, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_VOD_PLATFORM, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_THEME, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_HTML_TITLE, TRUE);
|
|
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_ART, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_ART_VARIANTS, TRUE);
|
|
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_ICON, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_ICON_NORMAL, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_ICON_FOCUSED, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_ICON_DISABLED, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_ICON_TYPE, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_ICON_VARIANTS, TRUE);
|
|
// TODO(matt): Sort out this title list stuff
|
|
//
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE_LIST_DELIMITER, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE_LIST_PREFIX, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE_LIST_SUFFIX, TRUE);
|
|
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE_SUFFIX, TRUE);
|
|
//
|
|
////
|
|
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_CREDIT, FALSE);
|
|
PushTypeSpecField(Project, FT_SCOPE, IDENT_INCLUDE, FALSE);
|
|
PushTypeSpecField(Project, FT_SCOPE, IDENT_MEDIUM, FALSE);
|
|
PushTypeSpecField(Project, FT_SCOPE, IDENT_PROJECT, FALSE);
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
PrintTypeField(config_type_field *F, config_identifier_id ParentScopeID, int IndentationLevel)
|
|
{
|
|
IndentedCarriageReturn(IndentationLevel);
|
|
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[F->ID].String);
|
|
PrintC(CS_BLACK_BOLD, " [");
|
|
if(F->Type == FT_NUMBER)
|
|
{
|
|
PrintC(CS_BLUE_BOLD, FieldTypeNames[F->Type]);
|
|
}
|
|
else if(F->Type == FT_BARE)
|
|
{
|
|
PrintC(CS_BLUE, FieldTypeNames[F->Type]);
|
|
}
|
|
else{
|
|
Colourise(CS_GREEN_BOLD);
|
|
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((F->ID == IDENT_NAME) && ParentScopeID == IDENT_ROLE)
|
|
{
|
|
if(!ConfigIdentifiers[F->ID].IdentifierDescription_RoleDisplayed)
|
|
{
|
|
++IndentationLevel;
|
|
IndentedCarriageReturn(IndentationLevel);
|
|
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[F->ID].IdentifierDescription_Role));
|
|
ConfigIdentifiers[F->ID].IdentifierDescription_RoleDisplayed = TRUE;
|
|
--IndentationLevel;
|
|
}
|
|
}
|
|
else if((F->ID == IDENT_ROLE) && ParentScopeID == IDENT_CREDIT)
|
|
{
|
|
if(!ConfigIdentifiers[F->ID].IdentifierDescription_CreditDisplayed)
|
|
{
|
|
++IndentationLevel;
|
|
IndentedCarriageReturn(IndentationLevel);
|
|
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[F->ID].IdentifierDescription_Credit));
|
|
ConfigIdentifiers[F->ID].IdentifierDescription_CreditDisplayed = TRUE;
|
|
--IndentationLevel;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!ConfigIdentifiers[F->ID].IdentifierDescriptionDisplayed)
|
|
{
|
|
++IndentationLevel;
|
|
IndentedCarriageReturn(IndentationLevel);
|
|
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[F->ID].IdentifierDescription));
|
|
ConfigIdentifiers[F->ID].IdentifierDescriptionDisplayed = TRUE;
|
|
--IndentationLevel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
PrintTypeSpec(config_type_spec *S, int IndentationLevel)
|
|
{
|
|
if(S->ID != IDENT_NULL)
|
|
{
|
|
IndentedCarriageReturn(IndentationLevel);
|
|
++IndentationLevel;
|
|
PrintStringC(CS_CYAN, Wrap0(ConfigIdentifiers[S->ID].String));
|
|
}
|
|
|
|
if(ConfigIdentifiers[S->ID].IdentifierDescription && !ConfigIdentifiers[S->ID].IdentifierDescriptionDisplayed)
|
|
{
|
|
IndentedCarriageReturn(IndentationLevel);
|
|
TypesetString(INDENT_WIDTH * IndentationLevel, Wrap0(ConfigIdentifiers[S->ID].IdentifierDescription));
|
|
ConfigIdentifiers[S->ID].IdentifierDescriptionDisplayed = TRUE;
|
|
}
|
|
|
|
for(int i = 0; i < S->Field.ItemCount; ++i)
|
|
{
|
|
config_type_field *This = GetPlaceInBook(&S->Field, i);
|
|
if(!(S->ID == IDENT_NULL && This->Type == FT_SCOPE))
|
|
{
|
|
PrintTypeField(This, S->ID, IndentationLevel);
|
|
}
|
|
}
|
|
|
|
if(S->ID != IDENT_NULL) { --IndentationLevel; }
|
|
}
|
|
|
|
void
|
|
PrintTypeSpecs(memory_book *TypeSpecs, int IndentationLevel)
|
|
{
|
|
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 < TypeSpecs->ItemCount; ++i)
|
|
{
|
|
PrintTypeSpec(GetPlaceInBook(TypeSpecs, i), IndentationLevel);
|
|
}
|
|
}
|
|
|
|
void
|
|
FreeTypeSpecs(memory_book *TypeSpecs)
|
|
{
|
|
for(int i = 0; i < TypeSpecs->ItemCount; ++i)
|
|
{
|
|
config_type_spec *This = GetPlaceInBook(TypeSpecs, i);
|
|
FreeBook(&This->Field);
|
|
}
|
|
FreeBook(TypeSpecs);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
char *Filename;
|
|
uint64_t LineNumber;
|
|
} token_position;
|
|
|
|
typedef enum
|
|
{
|
|
PT_BOOL,
|
|
PT_INT64,
|
|
PT_STRING,
|
|
} pair_type;
|
|
|
|
typedef struct
|
|
{
|
|
config_identifier_id Key;
|
|
union
|
|
{
|
|
string String;
|
|
int64_t int64_t;
|
|
bool bool;
|
|
};
|
|
token_position Position;
|
|
pair_type Type;
|
|
} config_pair;
|
|
|
|
typedef struct scope_tree
|
|
{
|
|
config_pair ID;
|
|
_memory_book(scope_tree) Trees;
|
|
_memory_book(config_pair) Pairs;
|
|
struct scope_tree *Parent;
|
|
config_type_spec TypeSpec;
|
|
//token_position Position; // NOTE(matt): We chose not to make ScopeTokens() set this value
|
|
} scope_tree;
|
|
|
|
config_type_spec *
|
|
GetTypeSpec(memory_book *TypeSpecs, config_identifier_id ID)
|
|
{
|
|
config_type_spec *Result = 0;
|
|
for(int i = 0; i < TypeSpecs->ItemCount; ++i)
|
|
{
|
|
config_type_spec *This = GetPlaceInBook(TypeSpecs, i);
|
|
if(This->ID == ID)
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
SetTypeSpec(scope_tree *Type, memory_book *TypeSpecs)
|
|
{
|
|
config_type_spec *Spec = GetTypeSpec(TypeSpecs, Type->ID.Key);
|
|
//PrintTypeSpec(Spec);
|
|
Type->TypeSpec.ID = Spec->ID;
|
|
Type->TypeSpec.Permeable = Spec->Permeable;
|
|
Type->TypeSpec.Field = InitBook(sizeof(config_type_field), 4);
|
|
for(int i = 0; i < Spec->Field.ItemCount; ++i)
|
|
{
|
|
config_type_field *Src = GetPlaceInBook(&Spec->Field, i);
|
|
config_type_field *Dest = MakeSpaceInBook(&Type->TypeSpec.Field);
|
|
|
|
Dest->Type = Src->Type;
|
|
Dest->ID = Src->ID;
|
|
Dest->Singleton = Src->Singleton;
|
|
Dest->IsSetLocally = Src->IsSetLocally;
|
|
}
|
|
}
|
|
|
|
config_type_field *
|
|
CurrentFieldIsInSpec(config_type_spec *Spec, tokens *T)
|
|
{
|
|
config_type_field *Result = 0;
|
|
if(Spec)
|
|
{
|
|
token *Token = GetPlaceInBook(&T->Token, T->CurrentIndex);
|
|
for(int i = 0; i < Spec->Field.ItemCount; ++i)
|
|
{
|
|
config_type_field *Field = GetPlaceInBook(&Spec->Field, i);
|
|
if(!StringsDifferLv0(Token->Content, ConfigIdentifiers[Field->ID].String))
|
|
{
|
|
Result = Field;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
string Filename;
|
|
memory_book Allow;
|
|
memory_book Deny;
|
|
} config_include;
|
|
|
|
bool
|
|
ParseMultiStringAssignment(tokens *T, memory_book *Dest, config_type_field *Field)
|
|
{
|
|
++T->CurrentIndex;
|
|
token Token = {};
|
|
if(!ExpectToken(T, TOKEN_ASSIGN, &Token))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if(!ExpectToken(T, TOKEN_STRING, &Token))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
string *This = MakeSpaceInBook(Dest);
|
|
*This = Token.Content;
|
|
|
|
Field->IsSetLocally = TRUE;
|
|
|
|
if(!ExpectToken(T, TOKEN_SEMICOLON, &Token))
|
|
{
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
PrintCurrentToken(colour_code C, tokens *T)
|
|
{
|
|
Colourise(C);
|
|
PrintToken(GetPlaceInBook(&T->Token, T->CurrentIndex), T->CurrentIndex);
|
|
Colourise(CS_END);
|
|
}
|
|
|
|
bool
|
|
DepartAssignment(tokens *T)
|
|
{
|
|
uint64_t ScopeLevel = 0;
|
|
do {
|
|
if(TokenIs(T, TOKEN_IDENTIFIER))
|
|
{
|
|
++T->CurrentIndex;
|
|
if(!ExpectToken(T, TOKEN_ASSIGN, 0))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if(TokenIs(T, TOKEN_STRING) || TokenIs(T, TOKEN_NUMBER))
|
|
{
|
|
++T->CurrentIndex;
|
|
}
|
|
else
|
|
{
|
|
ConfigErrorExpectation(T, TOKEN_STRING, TOKEN_NUMBER);
|
|
return FALSE;
|
|
}
|
|
|
|
if(TokenIs(T, TOKEN_SEMICOLON))
|
|
{
|
|
++T->CurrentIndex;
|
|
}
|
|
else if(TokenIs(T, TOKEN_OPEN_BRACE))
|
|
{
|
|
++ScopeLevel;
|
|
++T->CurrentIndex;
|
|
}
|
|
else
|
|
{
|
|
ConfigErrorExpectation(T, TOKEN_SEMICOLON, TOKEN_OPEN_BRACE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if(TokenIs(T, TOKEN_CLOSE_BRACE))
|
|
{
|
|
--ScopeLevel;
|
|
++T->CurrentIndex;
|
|
}
|
|
else
|
|
{
|
|
ConfigErrorExpectation(T, TOKEN_IDENTIFIER, TOKEN_CLOSE_BRACE);
|
|
return FALSE;
|
|
}
|
|
} while(ScopeLevel > 0);
|
|
return TRUE;
|
|
}
|
|
|
|
bool
|
|
DepartIncludeAssignment(tokens *T, uint64_t IncludeIdentifierTokenIndex)
|
|
{
|
|
T->CurrentIndex = IncludeIdentifierTokenIndex;
|
|
return(DepartAssignment(T));
|
|
}
|
|
|
|
void
|
|
FreeString(string *S)
|
|
{
|
|
Free(S->Base);
|
|
S->Length = 0;
|
|
}
|
|
|
|
void
|
|
FreeInclude(config_include *I)
|
|
{
|
|
FreeBook(&I->Allow);
|
|
FreeBook(&I->Deny);
|
|
free(I);
|
|
I = 0;
|
|
}
|
|
|
|
bool
|
|
IsStringEmpty(string *S)
|
|
{
|
|
return S ? S->Length == 0 : TRUE;
|
|
}
|
|
|
|
void
|
|
FreeScopeTreeRecursively(scope_tree *T)
|
|
{
|
|
FreeBook(&T->Pairs);
|
|
FreeBook(&T->TypeSpec.Field);
|
|
//Free(T->Position.Filename);
|
|
for(int i = 0; i < T->Trees.ItemCount; ++i)
|
|
{
|
|
FreeScopeTreeRecursively(GetPlaceInBook(&T->Trees, i));
|
|
}
|
|
FreeBook(&T->Trees);
|
|
}
|
|
|
|
void
|
|
FreeScopeTree(scope_tree *T)
|
|
{
|
|
FreeBook(&T->Pairs);
|
|
FreeBook(&T->TypeSpec.Field);
|
|
//Free(T->Position.Filename);
|
|
for(int i = 0; i < T->Trees.ItemCount; ++i)
|
|
{
|
|
FreeScopeTreeRecursively(GetPlaceInBook(&T->Trees, i));
|
|
}
|
|
FreeBook(&T->Trees);
|
|
Free(T);
|
|
}
|
|
|
|
void
|
|
PrintPair(config_pair *P, char *Delimiter, bool FillSyntax, uint64_t Indentation, bool PrependNewline, bool AppendNewline)
|
|
{
|
|
if(PrependNewline) { fprintf(stderr, "\n"); }
|
|
Indent(Indentation);
|
|
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[P->Key].String);
|
|
fprintf(stderr, "%s", Delimiter);
|
|
if(P->Type == PT_BOOL)
|
|
{
|
|
if(P->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, ";"); }
|
|
}
|
|
else if(P->Type == PT_INT64)
|
|
{
|
|
switch(P->Key)
|
|
{
|
|
case IDENT_GENRE:
|
|
{
|
|
Colourise(CS_GREEN_BOLD);
|
|
if(FillSyntax) { fprintf(stderr, "\""); }
|
|
fprintf(stderr, "%s", GenreStrings[P->int64_t]);
|
|
if(FillSyntax) { fprintf(stderr, "\""); }
|
|
} break;
|
|
case IDENT_LOG_LEVEL:
|
|
{
|
|
Colourise(CS_GREEN_BOLD);
|
|
if(FillSyntax) { fprintf(stderr, "\""); }
|
|
fprintf(stderr, "%s", LogLevelStrings[P->int64_t]);
|
|
if(FillSyntax) { fprintf(stderr, "\""); }
|
|
} break;
|
|
case IDENT_NUMBERING_SCHEME:
|
|
{
|
|
Colourise(CS_GREEN_BOLD);
|
|
if(FillSyntax) { fprintf(stderr, "\""); }
|
|
fprintf(stderr, "%s", NumberingSchemeStrings[P->int64_t]);
|
|
if(FillSyntax) { fprintf(stderr, "\""); }
|
|
} break;
|
|
default:
|
|
{
|
|
Colourise(CS_BLUE_BOLD);
|
|
fprintf(stderr, "%li", P->int64_t);
|
|
} break;
|
|
}
|
|
Colourise(CS_END);
|
|
if(FillSyntax) { fprintf(stderr, ";"); }
|
|
}
|
|
else if(P->Type == PT_STRING)
|
|
{
|
|
if(P->String.Length)
|
|
{
|
|
Colourise(CS_GREEN_BOLD);
|
|
if(FillSyntax) { fprintf(stderr, "\""); }
|
|
PrintString(P->String);
|
|
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->String.Length)
|
|
{
|
|
Colourise(CS_GREEN_BOLD);
|
|
fprintf(stderr, "\"");
|
|
PrintString(P->String);
|
|
fprintf(stderr, "\"");
|
|
Colourise(CS_END);
|
|
IndentedCarriageReturn(Indentation);
|
|
fprintf(stderr, "{");
|
|
}
|
|
else
|
|
{
|
|
PrintC(CS_YELLOW, "[unset]");
|
|
}
|
|
}
|
|
|
|
void
|
|
PrintBool(char *Title, bool Bool, char *Delimiter, bool FillSyntax, uint64_t Indentation, bool PrependNewline, bool AppendNewline)
|
|
{
|
|
if(PrependNewline) { 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(AppendNewline) { fprintf(stderr, "\n"); }
|
|
}
|
|
|
|
void PrintScopeTree(scope_tree *T, int IndentationLevel);
|
|
void
|
|
PrintScopeTree(scope_tree *T, int IndentationLevel)
|
|
{
|
|
if(T)
|
|
{
|
|
if(T->ID.Key != IDENT_NULL)
|
|
{
|
|
PrintScopeID(&T->ID, " = ", IndentationLevel);
|
|
++IndentationLevel;
|
|
}
|
|
bool ShouldPrependNewline = TRUE;
|
|
bool ShouldFillSyntax = TRUE;
|
|
for(int i = 0; i < T->Pairs.ItemCount; ++i)
|
|
{
|
|
PrintPair(GetPlaceInBook(&T->Pairs, i), " = ", ShouldFillSyntax, IndentationLevel, ShouldPrependNewline, FALSE);
|
|
}
|
|
for(int i = 0; i < T->Trees.ItemCount; ++i)
|
|
{
|
|
PrintScopeTree(GetPlaceInBook(&T->Trees, i), IndentationLevel);
|
|
}
|
|
if(T->ID.Key != IDENT_NULL)
|
|
{
|
|
--IndentationLevel;
|
|
IndentedCarriageReturn(IndentationLevel);
|
|
fprintf(stderr, "}");
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
PushPair(scope_tree *Parent, config_pair *P)
|
|
{
|
|
config_pair *New = MakeSpaceInBook(&Parent->Pairs);
|
|
*New = *P;
|
|
}
|
|
|
|
config_pair *
|
|
GetPair(scope_tree *Parent, config_identifier_id FieldID)
|
|
{
|
|
config_pair *Result = 0;
|
|
for(int i = 0; i < Parent->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *This = GetPlaceInBook(&Parent->Pairs, i);
|
|
if(This->Key == FieldID)
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
InitScopeBooks(scope_tree *Scope)
|
|
{
|
|
Scope->Trees = InitBook(sizeof(scope_tree), 4);
|
|
Scope->Pairs = InitBook(sizeof(config_pair), 4);
|
|
}
|
|
|
|
scope_tree *
|
|
InitRootScopeTree(void)
|
|
{
|
|
scope_tree *Result = calloc(1, sizeof(scope_tree));
|
|
InitScopeBooks(Result);
|
|
return Result;
|
|
}
|
|
|
|
scope_tree *
|
|
PushScope(memory_book *TypeSpecs, scope_tree *Parent, config_pair *ID)
|
|
{
|
|
scope_tree *This = MakeSpaceInBook(&Parent->Trees);
|
|
InitScopeBooks(This);
|
|
|
|
This->ID = *ID;
|
|
SetTypeSpec(This, TypeSpecs);
|
|
This->Parent = Parent;
|
|
|
|
return(This);
|
|
}
|
|
|
|
config_identifier_id
|
|
GetConfigIdentifierFromToken(token *T)
|
|
{
|
|
for(config_identifier_id i = 0; i < IDENT_COUNT; ++i)
|
|
{
|
|
if(!StringsDifferLv0(T->Content, ConfigIdentifiers[i].String))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return IDENT_NULL;
|
|
}
|
|
|
|
config_identifier_id
|
|
GetConfigIdentifierFromString(string S)
|
|
{
|
|
for(config_identifier_id i = 0; i < IDENT_COUNT; ++i)
|
|
{
|
|
if(!StringsDifferLv0(S, ConfigIdentifiers[i].String))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return IDENT_NULL;
|
|
}
|
|
|
|
config_type_field *
|
|
FieldIsInSpec(config_type_spec *Spec, config_identifier_id ID)
|
|
{
|
|
config_type_field *Result = 0;
|
|
for(int i = 0; i < Spec->Field.ItemCount; ++i)
|
|
{
|
|
config_type_field *This = GetPlaceInBook(&Spec->Field, i);
|
|
if(ID == This->ID)
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
bool
|
|
PairsMatch(config_pair *A, config_pair *B)
|
|
{
|
|
bool Result = FALSE;
|
|
if(A->Type == B->Type && A->Key == B->Key)
|
|
{
|
|
switch(A->Type)
|
|
{
|
|
case PT_BOOL:
|
|
{
|
|
Result = A->bool == B->bool;
|
|
} break;
|
|
case PT_INT64:
|
|
{
|
|
Result = A->int64_t == B->int64_t;
|
|
} break;
|
|
case PT_STRING:
|
|
{
|
|
Result = StringsMatch(A->String, B->String);
|
|
} break;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
scope_tree *
|
|
GetScope(scope_tree *T, config_pair *ID, bool IsSingleton)
|
|
{
|
|
scope_tree *Result = 0;
|
|
for(int i = 0; i < T->Trees.ItemCount; ++i)
|
|
{
|
|
scope_tree *This = GetPlaceInBook(&T->Trees, i);
|
|
if(IsSingleton)
|
|
{
|
|
if(ID->Key == This->ID.Key)
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(PairsMatch(ID, &This->ID))
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
config_pair *
|
|
GetAssignment(scope_tree *T, config_pair *Assignment, bool IsSingleton)
|
|
{
|
|
config_pair *Result = 0;
|
|
for(int i = 0; i < T->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *This = GetPlaceInBook(&T->Pairs, i);
|
|
if(IsSingleton)
|
|
{
|
|
if(Assignment->Type == This->Type && Assignment->Key == This->Key)
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(PairsMatch(Assignment, This))
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
scope_tree *CopyScope(memory_book *TypeSpecs, scope_tree *Dest, scope_tree *Src);
|
|
void
|
|
CopyScopeContents(memory_book *TypeSpecs, scope_tree *Dest, scope_tree *Src)
|
|
{
|
|
for(int i = 0; i < Src->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *Pair = GetPlaceInBook(&Src->Pairs, i);
|
|
PushPair(Dest, Pair);
|
|
}
|
|
for(int i = 0; i < Src->Trees.ItemCount; ++i)
|
|
{
|
|
scope_tree *Tree = GetPlaceInBook(&Src->Trees, i);
|
|
CopyScope(TypeSpecs, Dest, Tree);
|
|
}
|
|
}
|
|
|
|
scope_tree *
|
|
CopyScope(memory_book *TypeSpecs, scope_tree *Dest, scope_tree *Src)
|
|
{
|
|
Dest = PushScope(TypeSpecs, Dest, &Src->ID);
|
|
CopyScopeContents(TypeSpecs, Dest, Src);
|
|
return Dest;
|
|
}
|
|
|
|
void
|
|
MergeScopes(memory_book *TypeSpecs, scope_tree *Dest, scope_tree *Src, bool Intergenerational)
|
|
{
|
|
if(!Intergenerational || Dest->TypeSpec.Permeable)
|
|
{
|
|
for(int i = 0; i < Src->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *Pair = GetPlaceInBook(&Src->Pairs, i);
|
|
config_type_field *Field = FieldIsInSpec(&Dest->TypeSpec, Pair->Key);
|
|
if(Field)
|
|
{
|
|
config_pair *ThisAssignment = GetAssignment(Dest, Pair, Field->Singleton);
|
|
if(!ThisAssignment) { PushPair(Dest, Pair); }
|
|
}
|
|
}
|
|
for(int i = 0; i < Src->Trees.ItemCount; ++i)
|
|
{
|
|
scope_tree *Tree = GetPlaceInBook(&Src->Trees, i);
|
|
config_type_field *Field = FieldIsInSpec(&Dest->TypeSpec, Tree->ID.Key);
|
|
if(Field && !(Intergenerational && Tree->ID.Key == Dest->ID.Key))
|
|
{
|
|
scope_tree *ThisAssignment = GetScope(Dest, &Tree->ID, Field->Singleton);
|
|
if(!ThisAssignment) { CopyScope(TypeSpecs, Dest, Tree); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef enum
|
|
{
|
|
CRT_NULL,
|
|
CRT_ALLOW,
|
|
CRT_DENY,
|
|
} config_rule_type;
|
|
|
|
typedef struct
|
|
{
|
|
config_rule_type Scheme;
|
|
_memory_book(config_identifier_id) ID;
|
|
} config_include_rules;
|
|
|
|
void
|
|
PushRule(config_include_rules *R, config_identifier_id ID)
|
|
{
|
|
for(int i = 0; i < R->ID.ItemCount; ++i)
|
|
{
|
|
config_identifier_id *This = GetPlaceInBook(&R->ID, i);
|
|
if(ID == *This)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
config_identifier_id *This = MakeSpaceInBook(&R->ID);
|
|
*This = ID;
|
|
}
|
|
|
|
void
|
|
ParseRuleString(string *Filename, memory_book *TypeSpecs, config_type_spec *ParentTypeSpec, config_include_rules *Rules, token *RuleString)
|
|
{
|
|
int i = SkipWhitespaceS(&RuleString->Content, 0);
|
|
config_type_field *Field = 0;
|
|
for(; i < RuleString->Content.Length; i = SkipWhitespaceS(&RuleString->Content, i))
|
|
{
|
|
if(IsValidIdentifierCharacter(RuleString->Content.Base[i]))
|
|
{
|
|
string S = {};
|
|
S.Base = RuleString->Content.Base + i;
|
|
for(; i < RuleString->Content.Length && IsValidIdentifierCharacter(RuleString->Content.Base[i]); ++i)
|
|
{
|
|
++S.Length;
|
|
}
|
|
config_identifier_id ID;
|
|
if((ID = GetConfigIdentifierFromString(S)) != IDENT_NULL)
|
|
{
|
|
if((Field = FieldIsInSpec(ParentTypeSpec, ID)))
|
|
{
|
|
PushRule(Rules, ID);
|
|
if(i == RuleString->Content.Length && (ParentTypeSpec = GetTypeSpec(TypeSpecs, Field->ID)))
|
|
{
|
|
for(int j = 0; j < ParentTypeSpec->Field.ItemCount; ++j)
|
|
{
|
|
config_type_field *ParentField = GetPlaceInBook(&ParentTypeSpec->Field, j);
|
|
PushRule(Rules, ParentField->ID);
|
|
config_type_spec *ThisTypeSpec = GetTypeSpec(TypeSpecs, ParentField->ID);
|
|
if(ThisTypeSpec)
|
|
{
|
|
for(int k = 0; k < ThisTypeSpec->Field.ItemCount; ++k)
|
|
{
|
|
config_type_field *ThisField = GetPlaceInBook(&ThisTypeSpec->Field, k);
|
|
PushRule(Rules, ThisField->ID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ConfigError(Filename, RuleString->LineNumber, S_WARNING, "Field not in spec: ", &S);
|
|
PrintTypeSpec(ParentTypeSpec, 0);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ConfigError(Filename, RuleString->LineNumber, S_WARNING, "Unknown identifier: ", &S);
|
|
return;
|
|
}
|
|
}
|
|
else if(RuleString->Content.Base[i] == '.')
|
|
{
|
|
if(Field)
|
|
{
|
|
ParentTypeSpec = GetTypeSpec(TypeSpecs, Field->ID);
|
|
++i;
|
|
if(!ParentTypeSpec)
|
|
{
|
|
string This = Wrap0(ConfigIdentifiers[Field->ID].String);
|
|
ConfigError(Filename, RuleString->LineNumber, S_WARNING, "Invalid permission, type has no members: ", &This);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ConfigError(Filename, RuleString->LineNumber, S_WARNING, "Malformed rule string. \".\" must be preceded by a valid type", 0);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string Char = GetUTF8Character(RuleString->Content.Base + i, RuleString->Content.Length - i);
|
|
ConfigError(Filename, RuleString->LineNumber, S_WARNING, "Invalid character in include rule: ", &Char);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc
|
|
ParseIncludeRules(memory_book *TypeSpecs, config_type_spec *ParentTypeSpec, tokens *T, config_include_rules *Rules)
|
|
{
|
|
uint64_t IncludeIdentifierTokenIndex = T->CurrentIndex - 3;
|
|
if(TokenIs(T, TOKEN_SEMICOLON))
|
|
{
|
|
++T->CurrentIndex;
|
|
return RC_SUCCESS;
|
|
}
|
|
else if(TokenIs(T, TOKEN_OPEN_BRACE))
|
|
{
|
|
++T->CurrentIndex;
|
|
for(; T->CurrentIndex < T->Token.ItemCount;)
|
|
{
|
|
string Filepath = Wrap0(T->File.Path);
|
|
if(TokenIs(T, TOKEN_IDENTIFIER))
|
|
{
|
|
if(TokenEquals(T, "allow"))
|
|
{
|
|
if(Rules->Scheme == CRT_DENY)
|
|
{
|
|
FreeBook(&Rules->ID);
|
|
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
|
|
ConfigError(&Filepath, This->LineNumber, S_WARNING, "Mixture of allow and deny identifiers in \"include\" scope", 0);
|
|
return DepartIncludeAssignment(T, IncludeIdentifierTokenIndex) ? RC_SCHEME_MIXTURE : RC_SYNTAX_ERROR;
|
|
}
|
|
Rules->Scheme = CRT_ALLOW;
|
|
}
|
|
else if(TokenEquals(T, "deny"))
|
|
{
|
|
if(Rules->Scheme == CRT_ALLOW)
|
|
{
|
|
FreeBook(&Rules->ID);
|
|
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
|
|
ConfigError(&Filepath, This->LineNumber, S_WARNING, "Mixture of allow and deny identifiers in \"include\" scope", 0);
|
|
return DepartIncludeAssignment(T, IncludeIdentifierTokenIndex) ? RC_SCHEME_MIXTURE : RC_SYNTAX_ERROR;
|
|
}
|
|
Rules->Scheme = CRT_DENY;
|
|
}
|
|
else
|
|
{
|
|
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
|
|
ConfigError(&Filepath, This->LineNumber, S_WARNING, "Invalid identifier in \"include\" scope: ", &This->Content);
|
|
fprintf(stderr,
|
|
" Valid identifiers:\n"
|
|
" allow\n"
|
|
" deny\n");
|
|
FreeBook(&Rules->ID);
|
|
return DepartIncludeAssignment(T, IncludeIdentifierTokenIndex) ? RC_INVALID_IDENTIFIER : RC_SYNTAX_ERROR;
|
|
}
|
|
++T->CurrentIndex;
|
|
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeBook(&Rules->ID); return RC_SYNTAX_ERROR; }
|
|
token RuleString = {};
|
|
if(!ExpectToken(T, TOKEN_STRING, &RuleString)) { FreeBook(&Rules->ID); return RC_SYNTAX_ERROR; }
|
|
ParseRuleString(&Filepath, TypeSpecs, ParentTypeSpec, Rules, &RuleString);
|
|
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeBook(&Rules->ID); return RC_SYNTAX_ERROR; }
|
|
}
|
|
else if(TokenIs(T, TOKEN_CLOSE_BRACE))
|
|
{
|
|
++T->CurrentIndex;
|
|
return RC_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
FreeBook(&Rules->ID);
|
|
return RC_SYNTAX_ERROR;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FreeBook(&Rules->ID);
|
|
ConfigErrorExpectation(T, TOKEN_SEMICOLON, TOKEN_OPEN_BRACE);
|
|
return RC_SYNTAX_ERROR;
|
|
}
|
|
return RC_SUCCESS;
|
|
}
|
|
|
|
bool
|
|
FieldIsPermitted(config_include_rules *Rules, config_type_field *Field)
|
|
{
|
|
for(int i = 0; i < Rules->ID.ItemCount; ++i)
|
|
{
|
|
config_identifier_id *This = GetPlaceInBook(&Rules->ID, i);
|
|
if(Field->ID == *This)
|
|
{
|
|
return Rules->Scheme == CRT_ALLOW ? TRUE : FALSE;
|
|
}
|
|
}
|
|
return Rules->Scheme == CRT_ALLOW ? FALSE : TRUE;
|
|
}
|
|
|
|
void
|
|
ResetFields(config_type_spec *S)
|
|
{
|
|
for(int i = 0; i < S->Field.ItemCount; ++i)
|
|
{
|
|
config_type_field *This = GetPlaceInBook(&S->Field, i);
|
|
This->IsSetLocally = FALSE;
|
|
}
|
|
}
|
|
|
|
scope_tree *
|
|
PushDefaultScope(memory_book *TypeSpecs, scope_tree *Parent, config_identifier_id Key, string Value)
|
|
{
|
|
config_pair ID = { .Key = Key, .String = Value, .Type = PT_STRING };
|
|
return PushScope(TypeSpecs, Parent, &ID);
|
|
}
|
|
|
|
void
|
|
PushDefaultPair(scope_tree *Parent, config_identifier_id Key, string Value)
|
|
{
|
|
config_pair Pair = { .Key = Key, .String = Value, .Type = PT_STRING };
|
|
return PushPair(Parent, &Pair);
|
|
}
|
|
|
|
void
|
|
PushDefaultIntPair(scope_tree *Parent, config_identifier_id Key, uint64_t Value)
|
|
{
|
|
config_pair IntPair = { .Key = Key, .int64_t = Value, .Type = PT_INT64 };
|
|
return PushPair(Parent, &IntPair);
|
|
}
|
|
|
|
void
|
|
PushDefaultBoolPair(scope_tree *Parent, config_identifier_id Key, bool Value)
|
|
{
|
|
config_pair BoolPair = { .Key = Key, .bool = Value, .Type = PT_BOOL };
|
|
return PushPair(Parent, &BoolPair);
|
|
}
|
|
|
|
#define DEFAULT_PRIVACY_CHECK_INTERVAL 60 * 4
|
|
void
|
|
SetDefaults(scope_tree *Root, memory_book *TypeSpecs)
|
|
{
|
|
scope_tree *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"));
|
|
|
|
scope_tree *RoleIndexer = PushDefaultScope(TypeSpecs, Root, IDENT_ROLE, Wrap0("indexer"));
|
|
PushDefaultPair(RoleIndexer, IDENT_NAME, Wrap0("Indexer"));
|
|
PushDefaultBoolPair(RoleIndexer, IDENT_NON_SPEAKING, TRUE);
|
|
PushDefaultIntPair(RoleIndexer, IDENT_POSITION, -1);
|
|
|
|
PushDefaultPair(Root, IDENT_QUERY_STRING, Wrap0("r"));
|
|
PushDefaultPair(Root, IDENT_THEME, Wrap0("$origin"));
|
|
PushDefaultPair(Root, IDENT_CACHE_DIR, Wrap0("$XDG_CACHE_HOME/cinera"));
|
|
PushDefaultPair(Root, IDENT_DB_LOCATION, Wrap0("$XDG_CONFIG_HOME/cinera/cinera.db"));
|
|
|
|
// 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);
|
|
}
|
|
|
|
void
|
|
AssignOrPushEnumValue(scope_tree *Parent, config_pair *Pair, bool IsSingleton, int Value)
|
|
{
|
|
Pair->int64_t = Value;
|
|
Pair->Type = PT_INT64;
|
|
config_pair *Assignment = 0;
|
|
if((Assignment = GetAssignment(Parent, Pair, IsSingleton)))
|
|
{
|
|
Assignment->int64_t = Pair->int64_t;
|
|
}
|
|
else
|
|
{
|
|
PushPair(Parent, Pair);
|
|
}
|
|
}
|
|
|
|
scope_tree *
|
|
ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *TypeSpecs, config_include_rules *Rules)
|
|
{
|
|
scope_tree *Parent = Tree;
|
|
for(T->CurrentIndex = 0; T->CurrentIndex < T->Token.ItemCount;)
|
|
{
|
|
string Filepath = Wrap0(T->File.Path);
|
|
if(TokenIs(T, TOKEN_IDENTIFIER))
|
|
{
|
|
config_type_field *Field = CurrentFieldIsInSpec(&Parent->TypeSpec, T);
|
|
if(Field)
|
|
{
|
|
if(!Rules || FieldIsPermitted(Rules, Field))
|
|
{
|
|
if(Field->IsSetLocally && Field->Singleton)
|
|
{
|
|
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
|
|
ConfigError(&Filepath, This->LineNumber, S_WARNING, "Field already set: ", &This->Content);
|
|
}
|
|
// NOTE(matt): If we get rid of FT_BARE, then the ++T->CurrentIndex that all cases perform could happen
|
|
// right here
|
|
config_pair Pair = {};
|
|
|
|
Pair.Key = Field->ID;
|
|
Pair.Position.Filename = T->File.Path;
|
|
|
|
switch(Field->Type)
|
|
{
|
|
case FT_BARE:
|
|
{
|
|
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
|
|
token Value = *This;
|
|
Pair.Position.LineNumber = Value.LineNumber;
|
|
Pair.Type = PT_STRING;
|
|
|
|
++T->CurrentIndex;
|
|
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
|
|
config_pair *Assignment = 0;
|
|
if(!(Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
|
|
{
|
|
PushPair(Parent, &Pair);
|
|
}
|
|
} break;
|
|
case FT_BOOLEAN:
|
|
{
|
|
++T->CurrentIndex;
|
|
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeScopeTree(Tree); return 0; }
|
|
|
|
token Value = {};
|
|
if(!ExpectToken(T, TOKEN_STRING, &Value)) { FreeScopeTree(Tree); return 0; }
|
|
Pair.Position.LineNumber = Value.LineNumber;
|
|
Pair.Type = PT_BOOL;
|
|
|
|
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
|
|
bool Bool = GetBoolFromString(&Filepath, &Value);
|
|
if(Bool != -1)
|
|
{
|
|
Pair.bool = Bool;
|
|
config_pair *Assignment = 0;
|
|
if((Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
|
|
{
|
|
Assignment->bool = Pair.bool;
|
|
}
|
|
else
|
|
{
|
|
PushPair(Parent, &Pair);
|
|
}
|
|
}
|
|
else { FreeScopeTree(Tree); return 0; }
|
|
} break;
|
|
case FT_STRING:
|
|
{
|
|
++T->CurrentIndex;
|
|
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeScopeTree(Tree); return 0; }
|
|
|
|
token Value = {};
|
|
if(!ExpectToken(T, TOKEN_STRING, &Value)) { FreeScopeTree(Tree); return 0; }
|
|
Pair.Position.LineNumber = Value.LineNumber;
|
|
|
|
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
|
|
|
|
if(Field->ID == IDENT_NUMBERING_SCHEME)
|
|
{
|
|
numbering_scheme NumberingScheme = GetNumberingSchemeFromString(&Filepath, &Value);
|
|
if(NumberingScheme != NS_COUNT)
|
|
{
|
|
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, NumberingScheme);
|
|
}
|
|
else { FreeScopeTree(Tree); return 0; }
|
|
}
|
|
else if(Field->ID == IDENT_LOG_LEVEL)
|
|
{
|
|
log_level LogLevel = GetLogLevelFromString(&Filepath, &Value);
|
|
if(LogLevel != LOG_COUNT)
|
|
{
|
|
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, LogLevel);
|
|
}
|
|
else { FreeScopeTree(Tree); return 0; }
|
|
}
|
|
else if(Field->ID == IDENT_GENRE)
|
|
{
|
|
genre Genre = GetGenreFromString(&Filepath, &Value);
|
|
if(Genre != GENRE_COUNT)
|
|
{
|
|
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, Genre);
|
|
}
|
|
else { FreeScopeTree(Tree); return 0; }
|
|
}
|
|
else if(Field->ID == IDENT_ICON_TYPE)
|
|
{
|
|
icon_type IconType = GetIconTypeFromString(&Filepath, &Value);
|
|
if(IconType != IT_COUNT)
|
|
{
|
|
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, IconType);
|
|
}
|
|
else { FreeScopeTree(Tree); return 0; }
|
|
}
|
|
else if(Field->ID == IDENT_VOD_PLATFORM)
|
|
{
|
|
vod_platform VODPlatform = GetVODPlatformFromString(&Filepath, &Value);
|
|
if(VODPlatform != VP_COUNT)
|
|
{
|
|
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, VODPlatform);
|
|
}
|
|
else { FreeScopeTree(Tree); return 0; }
|
|
}
|
|
else if(Field->ID == IDENT_ART_VARIANTS || Field->ID == IDENT_ICON_VARIANTS)
|
|
{
|
|
int64_t ArtVariants = ParseArtVariantsString(&Filepath, &Value);
|
|
if(ArtVariants != -1)
|
|
{
|
|
Pair.int64_t = ArtVariants;
|
|
Pair.Type = PT_INT64;
|
|
config_pair *Assignment = 0;
|
|
if((Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
|
|
{
|
|
Assignment->int64_t = Pair.int64_t;
|
|
}
|
|
else
|
|
{
|
|
PushPair(Parent, &Pair);
|
|
}
|
|
}
|
|
else { FreeScopeTree(Tree); return 0; }
|
|
}
|
|
else
|
|
{
|
|
Pair.String = Value.Content;
|
|
Pair.Type = PT_STRING;
|
|
config_pair *Assignment = 0;
|
|
|
|
if((Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
|
|
{
|
|
Assignment->String = Pair.String;
|
|
Assignment->Position.LineNumber = Pair.Position.LineNumber;
|
|
Assignment->Position.Filename = Pair.Position.Filename;
|
|
}
|
|
else
|
|
{
|
|
PushPair(Parent, &Pair);
|
|
}
|
|
}
|
|
} break;
|
|
case FT_NUMBER:
|
|
{
|
|
++T->CurrentIndex;
|
|
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeScopeTree(Tree); return 0; }
|
|
|
|
token Value = {};
|
|
bool IsNegative = FALSE;
|
|
if(TokenIs(T, TOKEN_MINUS))
|
|
{
|
|
IsNegative = TRUE;
|
|
++T->CurrentIndex;
|
|
}
|
|
if(!ExpectToken(T, TOKEN_NUMBER, &Value)) { FreeScopeTree(Tree); return 0; }
|
|
Pair.int64_t = IsNegative ? 0 - Value.int64_t : Value.int64_t;
|
|
Pair.Type = PT_INT64;
|
|
|
|
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
|
|
|
|
config_pair *Assignment = 0;
|
|
if((Assignment = GetAssignment(Parent, &Pair, Field->Singleton)))
|
|
{
|
|
Assignment->int64_t = Pair.int64_t;
|
|
}
|
|
else
|
|
{
|
|
PushPair(Parent, &Pair);
|
|
}
|
|
} break;
|
|
case FT_SCOPE:
|
|
{
|
|
++T->CurrentIndex;
|
|
if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeScopeTree(Tree); return 0; }
|
|
|
|
token Value = {};
|
|
if(!ExpectToken(T, TOKEN_STRING, &Value)) { FreeScopeTree(Tree); return 0; }
|
|
Pair.Type = PT_STRING;
|
|
Pair.String = Value.Content;
|
|
Pair.Position.LineNumber = Value.LineNumber;
|
|
|
|
if(Field->ID == IDENT_INCLUDE)
|
|
{
|
|
int IncludePathTokenIndex = T->CurrentIndex - 1;
|
|
config_include_rules Rules = {};
|
|
Rules.ID = InitBook(sizeof(config_identifier_id), 4);
|
|
switch(ParseIncludeRules(TypeSpecs, &Parent->TypeSpec, T, &Rules))
|
|
{
|
|
case RC_SUCCESS:
|
|
{
|
|
string IncluderFilePath = Wrap0(T->File.Path);
|
|
char *IncludePath = ExpandPath(Pair.String, &IncluderFilePath);
|
|
if(IncludePath)
|
|
{
|
|
string IncludePathL = Wrap0(IncludePath);
|
|
|
|
tokens *I = 0;
|
|
for(int i = 0; i < TokensList->ItemCount; ++i)
|
|
{
|
|
tokens *This = GetPlaceInBook(TokensList, i);
|
|
if(!StringsDifferLv0(Value.Content, This->File.Path))
|
|
{
|
|
I = This;
|
|
I->CurrentIndex = 0;
|
|
I->CurrentLine = 0;
|
|
}
|
|
}
|
|
if(!I)
|
|
{
|
|
I = Tokenise(TokensList, IncludePathL);
|
|
}
|
|
if(I)
|
|
{
|
|
Parent = ScopeTokens(Parent, TokensList, I, TypeSpecs, &Rules);
|
|
FreeBook(&Rules.ID);
|
|
if(!Parent)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
token *This = GetPlaceInBook(&T->Token, IncludePathTokenIndex);
|
|
ConfigFileIncludeError(&Filepath, This->LineNumber, Wrap0(IncludePath));
|
|
}
|
|
Free(IncludePath);
|
|
}
|
|
else
|
|
{
|
|
ConfigError(&IncluderFilePath, Pair.Position.LineNumber, S_WARNING, "Unable to include file: ", &Pair.String);
|
|
}
|
|
} break;
|
|
case RC_SYNTAX_ERROR:
|
|
{
|
|
FreeScopeTree(Tree); return 0;
|
|
} break;
|
|
case RC_SCHEME_MIXTURE:
|
|
case RC_INVALID_IDENTIFIER:
|
|
default: break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO(matt): Here is probably where we must fix deduplication of support scopes
|
|
scope_tree *Search = 0;
|
|
bool SelfContained = FALSE;
|
|
if(Field->ID == IDENT_SUPPORT)
|
|
{
|
|
scope_tree *ParentP = Parent;
|
|
Search = GetScope(ParentP, &Pair, Field->Singleton);
|
|
if(Search) { SelfContained = TRUE; }
|
|
while(!Search && ParentP->Parent)
|
|
{
|
|
ParentP = ParentP->Parent;
|
|
Search = GetScope(ParentP, &Pair, Field->Singleton);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Search = GetScope(Parent, &Pair, Field->Singleton);
|
|
}
|
|
|
|
|
|
if(Search)
|
|
{
|
|
if(Field->ID == IDENT_SUPPORT && !SelfContained)
|
|
{
|
|
Parent = CopyScope(TypeSpecs, Parent, Search);
|
|
}
|
|
else
|
|
{
|
|
Parent = Search;
|
|
MergeScopes(TypeSpecs, Parent, Parent->Parent, TRUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Parent = PushScope(TypeSpecs, Parent, &Pair);
|
|
if(Parent->ID.Key == IDENT_MEDIUM)
|
|
{
|
|
config_pair Hidden = {};
|
|
Hidden.Key = IDENT_HIDDEN;
|
|
Hidden.bool = FALSE;
|
|
Hidden.Type = PT_BOOL;
|
|
PushPair(Parent, &Hidden);
|
|
}
|
|
MergeScopes(TypeSpecs, Parent, Parent->Parent, TRUE);
|
|
}
|
|
|
|
ResetFields(&Parent->TypeSpec);
|
|
|
|
if(TokenIs(T, TOKEN_SEMICOLON))
|
|
{
|
|
Parent = Parent->Parent;
|
|
}
|
|
else if(TokenIs(T, TOKEN_OPEN_BRACE))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
ConfigErrorExpectation(T, TOKEN_SEMICOLON, TOKEN_OPEN_BRACE);
|
|
FreeScopeTree(Tree); return 0;
|
|
}
|
|
++T->CurrentIndex;
|
|
}
|
|
} break;
|
|
}
|
|
// NOTE(matt): This may not be appropriate for the IDENT_INCLUDE...
|
|
Field->IsSetLocally = TRUE;
|
|
}
|
|
else
|
|
{
|
|
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
|
|
ConfigError(&Filepath, This->LineNumber, S_WARNING, "Field not allowed: ", &This->Content);
|
|
DepartAssignment(T);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
|
|
ConfigError(&Filepath, This->LineNumber, S_WARNING, "Invalid field: ", &This->Content);
|
|
if(!DepartAssignment(T))
|
|
{
|
|
FreeScopeTree(Tree);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
else if(TokenIs(T, TOKEN_CLOSE_BRACE))
|
|
{
|
|
if(!Parent->Parent)
|
|
{
|
|
token *This = GetPlaceInBook(&T->Token, T->CurrentIndex);
|
|
ConfigError(&Filepath, This->LineNumber, S_ERROR, "Syntax error: Unpaired closing brace", 0);
|
|
FreeScopeTree(Tree);
|
|
return 0;
|
|
}
|
|
Parent = Parent->Parent;
|
|
++T->CurrentIndex;
|
|
}
|
|
else
|
|
{
|
|
ConfigErrorExpectation(T, TOKEN_IDENTIFIER, TOKEN_CLOSE_BRACE);
|
|
FreeScopeTree(Tree);
|
|
return 0;
|
|
}
|
|
}
|
|
return Tree;
|
|
}
|
|
|
|
// TODO(matt): Fully nail down the title list stuff. Perhaps, make the title_list_end; identifier somehow indicate to child
|
|
// scopes that they should not absorb any title_list stuff...
|
|
|
|
typedef struct
|
|
{
|
|
token_position Position;
|
|
config_identifier_id Variable;
|
|
} resolution_error;
|
|
|
|
typedef struct
|
|
{
|
|
_memory_book(resolution_error) Errors;
|
|
_memory_book(resolution_error) Warnings;
|
|
} resolution_errors;
|
|
|
|
void
|
|
PushError(resolution_errors *E, severity Severity, token_position *Position, config_identifier_id Variable)
|
|
{
|
|
if(Severity == S_WARNING)
|
|
{
|
|
resolution_error *This = MakeSpaceInBook(&E->Warnings);
|
|
if(Position)
|
|
{
|
|
This->Position = *Position;
|
|
}
|
|
This->Variable = Variable;
|
|
}
|
|
else
|
|
{
|
|
resolution_error *This = MakeSpaceInBook(&E->Errors);
|
|
if(Position)
|
|
{
|
|
This->Position = *Position;
|
|
}
|
|
This->Variable = Variable;
|
|
}
|
|
}
|
|
|
|
resolution_error *
|
|
GetError(resolution_errors *E, severity Severity, token_position *Position, config_identifier_id Variable)
|
|
{
|
|
resolution_error *Result = 0;
|
|
if(Severity == S_WARNING)
|
|
{
|
|
for(int i = 0; i < E->Warnings.ItemCount; ++i)
|
|
{
|
|
resolution_error *This = GetPlaceInBook(&E->Warnings, i);
|
|
if(Position->Filename == This->Position.Filename &&
|
|
Position->LineNumber == This->Position.LineNumber &&
|
|
Variable == This->Variable)
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(int i = 0; i < E->Errors.ItemCount; ++i)
|
|
{
|
|
resolution_error *This = GetPlaceInBook(&E->Errors, i);
|
|
if(Position->Filename == This->Position.Filename &&
|
|
Position->LineNumber == This->Position.LineNumber &&
|
|
Variable == This->Variable)
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
FreeErrors(resolution_errors *E)
|
|
{
|
|
FreeAndReinitialiseBook(&E->Errors);
|
|
FreeAndReinitialiseBook(&E->Warnings);
|
|
}
|
|
|
|
string
|
|
ResolveEnvironmentVariable(memory_book *M, string Variable)
|
|
{
|
|
string Result = {};
|
|
int DollarCharacterBytes = 1;
|
|
int NullTerminationBytes = 1;
|
|
// NOTE(matt): Stack-string
|
|
char Variable0[DollarCharacterBytes + Variable.Length + NullTerminationBytes];
|
|
char *Ptr = Variable0;
|
|
if(!StringsMatch(Variable, Wrap0("~")))
|
|
{
|
|
*Ptr++ = '$';
|
|
}
|
|
for(int i = 0; i < Variable.Length; ++i)
|
|
{
|
|
*Ptr++ = Variable.Base[i];
|
|
}
|
|
*Ptr = '\0';
|
|
int Flags = WRDE_NOCMD | WRDE_UNDEF | WRDE_APPEND;
|
|
wordexp_t Expansions = {};
|
|
wordexp(Variable0, &Expansions, Flags);
|
|
if(Expansions.we_wordc > 0)
|
|
{
|
|
Result = ExtendStringInBook(M, Wrap0(Expansions.we_wordv[0]));
|
|
}
|
|
wordfree(&Expansions);
|
|
return Result;
|
|
}
|
|
|
|
role *
|
|
GetRole(config *C, resolution_errors *E, config_pair *RoleID)
|
|
{
|
|
role *Result = 0;
|
|
for(int i = 0; i < C->Role.ItemCount; ++i)
|
|
{
|
|
role *Role = GetPlaceInBook(&C->Role, i);
|
|
if(!StringsDifferCaseInsensitive(Role->ID, RoleID->String))
|
|
{
|
|
Result = Role;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!Result && !GetError(E, S_WARNING, &RoleID->Position, IDENT_ROLE))
|
|
{
|
|
string Filepath = Wrap0(RoleID->Position.Filename);
|
|
ConfigError(&Filepath, RoleID->Position.LineNumber, S_WARNING, "Could not find role: ", &RoleID->String);
|
|
PushError(E, S_WARNING, &RoleID->Position, IDENT_ROLE);
|
|
WaitForInput();
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
role *
|
|
GetRoleByID(config *C, string ID)
|
|
{
|
|
role *Result = 0;
|
|
for(int i = 0; i < C->Role.ItemCount; ++i)
|
|
{
|
|
role *Role = GetPlaceInBook(&C->Role, i);
|
|
if(!StringsDifferCaseInsensitive(Role->ID, ID))
|
|
{
|
|
Result = Role;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!Result)
|
|
{
|
|
ConfigError(0, 0, S_WARNING, "Could not find role: ", &ID);
|
|
WaitForInput();
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
person *
|
|
GetPerson(config *C, resolution_errors *E, config_pair *PersonID)
|
|
{
|
|
person *Result = 0;
|
|
for(int i = 0; i < C->Person.ItemCount; ++i)
|
|
{
|
|
person *Person = GetPlaceInBook(&C->Person, i);
|
|
if(!StringsDifferCaseInsensitive(Person->ID, PersonID->String))
|
|
{
|
|
Result = Person;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!Result && !GetError(E, S_WARNING, &PersonID->Position, IDENT_PERSON))
|
|
{
|
|
string Filepath = Wrap0(PersonID->Position.Filename);
|
|
ConfigError(&Filepath, PersonID->Position.LineNumber, S_WARNING, "Could not find person: ", &PersonID->String);
|
|
PushError(E, S_WARNING, &PersonID->Position, IDENT_PERSON);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
string
|
|
DeriveLineageOfProject(config *C, scope_tree *Project)
|
|
{
|
|
string Result = {};
|
|
memory_book StringList = InitBookOfPointers(4);
|
|
if(Project)
|
|
{
|
|
string **Writer = MakeSpaceInBook(&StringList);
|
|
*Writer = &Project->ID.String;
|
|
Project = Project->Parent;
|
|
}
|
|
|
|
string Slash = Wrap0("/");
|
|
while(Project && Project->ID.Key == IDENT_PROJECT)
|
|
{
|
|
string **Writer = MakeSpaceInBook(&StringList);
|
|
*Writer = &Slash;
|
|
|
|
Writer = MakeSpaceInBook(&StringList);
|
|
*Writer = &Project->ID.String;
|
|
|
|
Project = Project->Parent;
|
|
}
|
|
|
|
for(int i = StringList.ItemCount - 1; i >= 0; --i)
|
|
{
|
|
string **Reader = GetPlaceInBook(&StringList, i);
|
|
Result = ExtendStringInBook(&C->ResolvedVariables, **Reader);;
|
|
}
|
|
FreeBook(&StringList);
|
|
return Result;
|
|
}
|
|
|
|
string
|
|
EmptyString(void)
|
|
{
|
|
string Result = {};
|
|
return Result;
|
|
}
|
|
|
|
string
|
|
DeriveLineageWithoutOriginOfProject(config *C, scope_tree *Project)
|
|
{
|
|
string Result = {};
|
|
memory_book StringList = InitBookOfPointers(4);
|
|
if(Project->Parent && Project->Parent->ID.Key == IDENT_PROJECT)
|
|
{
|
|
string **Writer = MakeSpaceInBook(&StringList);
|
|
*Writer = &Project->ID.String;
|
|
Project = Project->Parent;
|
|
}
|
|
|
|
string Slash = Wrap0("/");
|
|
while(Project->Parent && Project->Parent->ID.Key == IDENT_PROJECT)
|
|
{
|
|
string **Writer = MakeSpaceInBook(&StringList);
|
|
*Writer = &Slash;
|
|
|
|
Writer = MakeSpaceInBook(&StringList);
|
|
*Writer = &Project->ID.String;
|
|
Project = Project->Parent;
|
|
}
|
|
|
|
if(StringList.ItemCount > 0)
|
|
{
|
|
for(int i = StringList.ItemCount - 1; i >= 0; --i)
|
|
{
|
|
string **Reader = GetPlaceInBook(&StringList, i);
|
|
Result = ExtendStringInBook(&C->ResolvedVariables, **Reader);
|
|
}
|
|
FreeBook(&StringList);
|
|
}
|
|
else
|
|
{
|
|
Result = ExtendStringInBook(&C->ResolvedVariables, EmptyString());
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
string
|
|
ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_identifier_id Variable, token_position *Position)
|
|
{
|
|
string Result = {};
|
|
string Filepath = Wrap0(Position->Filename);
|
|
switch(Variable)
|
|
{
|
|
case IDENT_LINEAGE:
|
|
{
|
|
if(Scope->ID.Key == IDENT_PROJECT)
|
|
{
|
|
Result = DeriveLineageOfProject(C, Scope);
|
|
}
|
|
else
|
|
{
|
|
string This = Wrap0(ConfigIdentifiers[Variable].String);
|
|
if(!GetError(E, S_WARNING, Position, Variable))
|
|
{
|
|
ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This);
|
|
PushError(E, S_WARNING, Position, Variable);
|
|
}
|
|
}
|
|
} break;
|
|
case IDENT_LINEAGE_WITHOUT_ORIGIN:
|
|
{
|
|
if(Scope->ID.Key == IDENT_PROJECT)
|
|
{
|
|
Result = DeriveLineageWithoutOriginOfProject(C, Scope);
|
|
}
|
|
else
|
|
{
|
|
string This = Wrap0(ConfigIdentifiers[Variable].String);
|
|
if(!GetError(E, S_WARNING, Position, Variable))
|
|
{
|
|
ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This);
|
|
PushError(E, S_WARNING, Position, Variable);
|
|
}
|
|
}
|
|
} break;
|
|
case IDENT_ORIGIN:
|
|
{
|
|
if(Scope->ID.Key == IDENT_PROJECT)
|
|
{
|
|
while(Scope->Parent && Scope->Parent->ID.Key == IDENT_PROJECT)
|
|
{
|
|
Scope = Scope->Parent;
|
|
}
|
|
Result = ExtendStringInBook(&C->ResolvedVariables, Scope->ID.String);
|
|
}
|
|
else
|
|
{
|
|
string This = Wrap0(ConfigIdentifiers[Variable].String);
|
|
if(!GetError(E, S_WARNING, Position, Variable))
|
|
{
|
|
ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This);
|
|
PushError(E, S_WARNING, Position, Variable);
|
|
}
|
|
}
|
|
} break;
|
|
case IDENT_OWNER:
|
|
{
|
|
bool Processed = FALSE;
|
|
while(!Processed && Scope)
|
|
{
|
|
for(int i = 0; i < Scope->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *Pair = GetPlaceInBook(&Scope->Pairs, i);
|
|
if(Variable == Pair->Key)
|
|
{
|
|
if(GetPerson(C, E, Pair))
|
|
{
|
|
Result = ExtendStringInBook(&C->ResolvedVariables, Pair->String);
|
|
Processed = TRUE;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if(!GetError(E, S_WARNING, Position, Variable))
|
|
{
|
|
ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Owner set, but the person does not exist: ", &Pair->String);
|
|
PushError(E, S_WARNING, Position, Variable);
|
|
}
|
|
Processed = TRUE;
|
|
}
|
|
}
|
|
}
|
|
Scope = Scope->Parent;
|
|
}
|
|
if(!Processed)
|
|
{
|
|
if(!GetError(E, S_WARNING, Position, Variable))
|
|
{
|
|
ConfigError(&Filepath, Position->LineNumber, S_WARNING, "No owner set", 0);
|
|
PushError(E, S_WARNING, Position, Variable);
|
|
}
|
|
}
|
|
} break;
|
|
case IDENT_PERSON:
|
|
{
|
|
// NOTE(matt): Allow scopes within a person scope, e.g. support, to resolve $person
|
|
while(Scope && Scope->ID.Key != Variable)
|
|
{
|
|
Scope = Scope->Parent;
|
|
}
|
|
} // NOTE(matt): Intentional fall-through
|
|
case IDENT_PROJECT:
|
|
case IDENT_SUPPORT:
|
|
{
|
|
if(Scope && Scope->ID.Key == Variable)
|
|
{
|
|
Result = ExtendStringInBook(&C->ResolvedVariables, Scope->ID.String);
|
|
}
|
|
else
|
|
{
|
|
string This = Wrap0(ConfigIdentifiers[Variable].String);
|
|
if(!GetError(E, S_WARNING, Position, Variable))
|
|
{
|
|
ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This);
|
|
PushError(E, S_WARNING, Position, Variable);
|
|
}
|
|
}
|
|
} break;
|
|
default:
|
|
{
|
|
if(!GetError(E, S_WARNING, Position, Variable))
|
|
{
|
|
string This = Wrap0(ConfigIdentifiers[Variable].String);
|
|
ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Unhandled local variable: ", &This);
|
|
PushError(E, S_WARNING, Position, Variable);
|
|
}
|
|
} break;
|
|
};
|
|
|
|
return Result;
|
|
}
|
|
|
|
string
|
|
ResolveString(config *C, resolution_errors *E, scope_tree *Parent, config_pair *S, bool AbsoluteFilePath)
|
|
{
|
|
//PrintFunctionName("ResolveString()");
|
|
string Result = {};
|
|
ResetPen(&C->ResolvedVariables);
|
|
for(int i = 0; i < S->String.Length;)
|
|
{
|
|
switch(S->String.Base[i])
|
|
{
|
|
case '$':
|
|
{
|
|
string Test = {};
|
|
++i;
|
|
if(i < S->String.Length && S->String.Base[i] == '{')
|
|
{
|
|
++i;
|
|
Test.Base = S->String.Base + i;
|
|
while(i < S->String.Length && S->String.Base[i] != '}')
|
|
{
|
|
++Test.Length;
|
|
++i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Test.Base = S->String.Base + i;
|
|
while(i < S->String.Length && IsValidIdentifierCharacter(S->String.Base[i]))
|
|
{
|
|
++Test.Length;
|
|
++i;
|
|
}
|
|
if(i > 0) { --i; }
|
|
}
|
|
|
|
config_identifier_id ID = GetConfigIdentifierFromString(Test);
|
|
if(ID != IDENT_NULL)
|
|
{
|
|
Result = ResolveLocalVariable(C, E, Parent, ID, &S->Position);
|
|
}
|
|
else
|
|
{
|
|
Result = ResolveEnvironmentVariable(&C->ResolvedVariables, Test);
|
|
}
|
|
} break;
|
|
case '~':
|
|
{
|
|
string Char = { .Base = S->String.Base + i, .Length = 1 };
|
|
if(AbsoluteFilePath && i == 0)
|
|
{
|
|
Result = ResolveEnvironmentVariable(&C->ResolvedVariables, Char);
|
|
}
|
|
else
|
|
{
|
|
Result = ExtendStringInBook(&C->ResolvedVariables, Char);
|
|
}
|
|
} break;
|
|
case '\\': { ++i; }; // NOTE(matt): Intentional fall-through
|
|
default:
|
|
{
|
|
string Char = { .Base = S->String.Base + i, .Length = 1 };
|
|
Result = ExtendStringInBook(&C->ResolvedVariables, Char);
|
|
} break;
|
|
|
|
}
|
|
++i;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
typedef enum
|
|
{
|
|
P_ABS,
|
|
P_REL,
|
|
} path_relativity;
|
|
|
|
string
|
|
StripSlashes(string Path, path_relativity Relativity)
|
|
{
|
|
string Result = Path;
|
|
if(Relativity == P_REL)
|
|
{
|
|
while(Result.Length > 0 && *Result.Base == '/')
|
|
{
|
|
++Result.Base;
|
|
--Result.Length;
|
|
}
|
|
}
|
|
|
|
while(Result.Length > 0 && Result.Base[Result.Length - 1] == '/')
|
|
{
|
|
--Result.Length;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
bool
|
|
HasImageFileExtension(string Path)
|
|
{
|
|
return ExtensionMatches(Path, EXT_GIF) ||
|
|
ExtensionMatches(Path, EXT_JPEG) ||
|
|
ExtensionMatches(Path, EXT_JPG) ||
|
|
ExtensionMatches(Path, EXT_PNG);
|
|
}
|
|
|
|
typedef uint64_t variant;
|
|
typedef struct
|
|
{
|
|
string Path;
|
|
_memory_book(variant) Variants;
|
|
} variant_string;
|
|
|
|
void
|
|
PushVariantUniquely(resolution_errors *E, memory_book *VariantStrings, string Path, variant Variants, config_identifier_id Identifier)
|
|
{
|
|
if(Path.Length > 0)
|
|
{
|
|
bool FoundPath = FALSE;
|
|
for(int i = 0; i < VariantStrings->ItemCount; ++i)
|
|
{
|
|
variant_string *ThisVariantString = GetPlaceInBook(VariantStrings, i);
|
|
if(StringsMatch(ThisVariantString->Path, Path))
|
|
{
|
|
FoundPath = TRUE;
|
|
bool FoundVariantsMatch = FALSE;
|
|
for(int VariantsIndex = 0; VariantsIndex < ThisVariantString->Variants.ItemCount; ++VariantsIndex)
|
|
{
|
|
variant Test = *(variant *)GetPlaceInBook(&ThisVariantString->Variants, VariantsIndex);
|
|
if(Test == Variants)
|
|
{
|
|
FoundVariantsMatch = TRUE;
|
|
}
|
|
}
|
|
|
|
if(!FoundVariantsMatch)
|
|
{
|
|
variant *ThisVariant = MakeSpaceInBook(&ThisVariantString->Variants);
|
|
*ThisVariant = Variants;
|
|
PushError(E, S_ERROR, 0, Identifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!FoundPath)
|
|
{
|
|
variant_string *NewVariantString = MakeSpaceInBook(VariantStrings);
|
|
NewVariantString->Path = Path;
|
|
|
|
NewVariantString->Variants = InitBook(sizeof(variant), 4);
|
|
variant *NewVariants = MakeSpaceInBook(&NewVariantString->Variants);
|
|
*NewVariants = Variants;
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
string String0;
|
|
string String1;
|
|
_memory_book(project *) Projects;
|
|
} config_string_association;
|
|
|
|
typedef struct
|
|
{
|
|
config_identifier_id ID0;
|
|
config_identifier_id ID1;
|
|
_memory_book(config_string_association) Associations;
|
|
} config_string_associations;
|
|
|
|
typedef struct
|
|
{
|
|
_memory_book(variant_string) VariantStrings;
|
|
config_string_associations HMMLDirs;
|
|
config_string_associations BaseDirAndSearchLocation;
|
|
config_string_associations BaseDirAndPlayerLocation;
|
|
} config_verifiers;
|
|
|
|
void
|
|
PushMedium(config *C, resolution_errors *E, config_verifiers *V, project *P, scope_tree *MediumTree)
|
|
{
|
|
// TODO(matt): Do we need to warn about incomplete medium information?
|
|
medium *Medium = MakeSpaceInBook(&P->Medium);
|
|
Medium->ID = ResolveString(C, E, MediumTree, &MediumTree->ID, FALSE);
|
|
for(int i = 0; i < MediumTree->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *This = GetPlaceInBook(&MediumTree->Pairs, i);
|
|
switch(This->Key)
|
|
{
|
|
case IDENT_ICON: { Medium->Icon = ResolveString(C, E, MediumTree, This, FALSE); } break;
|
|
case IDENT_ICON_NORMAL: { Medium->IconNormal = ResolveString(C, E, MediumTree, This, FALSE); } break;
|
|
case IDENT_ICON_FOCUSED: { Medium->IconFocused = ResolveString(C, E, MediumTree, This, FALSE); } break;
|
|
case IDENT_ICON_DISABLED: { Medium->IconDisabled = ResolveString(C, E, MediumTree, This, FALSE); } break;
|
|
case IDENT_NAME: { Medium->Name = ResolveString(C, E, MediumTree, This, FALSE); } break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < MediumTree->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *This = GetPlaceInBook(&MediumTree->Pairs, i);
|
|
switch(This->Key)
|
|
{
|
|
// int64_t
|
|
case IDENT_ICON_TYPE: { Medium->IconType = This->int64_t; } break;
|
|
case IDENT_ICON_VARIANTS: { Medium->IconVariants = This->int64_t; } break;
|
|
|
|
// bool
|
|
case IDENT_HIDDEN: { Medium->Hidden = This->bool; } break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
if(Medium->Icon.Length > 0)
|
|
{
|
|
if(Medium->IconType == IT_DEFAULT_UNSET)
|
|
{
|
|
if(HasImageFileExtension(Medium->Icon))
|
|
{
|
|
Medium->IconType = IT_GRAPHICAL;
|
|
}
|
|
else
|
|
{
|
|
Medium->IconType = IT_TEXTUAL;
|
|
}
|
|
}
|
|
|
|
switch(Medium->IconType)
|
|
{
|
|
case IT_GRAPHICAL:
|
|
{
|
|
PushVariantUniquely(E, &V->VariantStrings, Medium->Icon, Medium->IconVariants, IDENT_ICON);
|
|
} break;
|
|
case IT_TEXTUAL:
|
|
{
|
|
if(Medium->IconNormal.Length == 0) { Medium->IconNormal = Medium->Icon; }
|
|
if(Medium->IconFocused.Length == 0) { Medium->IconFocused = Medium->Icon; }
|
|
if(Medium->IconDisabled.Length == 0) { Medium->IconDisabled = Medium->Icon; }
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
medium *
|
|
GetMediumFromProject(project *P, string ID)
|
|
{
|
|
medium *Result = 0;
|
|
for(int i = 0; i < P->Medium.ItemCount; ++i)
|
|
{
|
|
medium *This = GetPlaceInBook(&P->Medium, i);
|
|
if(StringsMatch(ID, This->ID))
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
PushSupport(config *C, resolution_errors *E, config_verifiers *V, person *P, scope_tree *SupportTree)
|
|
{
|
|
support *Support = MakeSpaceInBook(&P->Support);
|
|
Support->ID = ResolveString(C, E, SupportTree, &SupportTree->ID, FALSE);
|
|
for(int i = 0; i < SupportTree->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *This = GetPlaceInBook(&SupportTree->Pairs, i);
|
|
switch(This->Key)
|
|
{
|
|
case IDENT_ICON: { Support->Icon = StripSlashes(ResolveString(C, E, SupportTree, This, FALSE), P_REL); } break;
|
|
case IDENT_ICON_NORMAL: { Support->IconNormal = ResolveString(C, E, SupportTree, This, FALSE); } break;
|
|
case IDENT_ICON_FOCUSED: { Support->IconFocused = ResolveString(C, E, SupportTree, This, FALSE); } break;
|
|
case IDENT_URL: { Support->URL = ResolveString(C, E, SupportTree, This, FALSE); } break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < SupportTree->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *This = GetPlaceInBook(&SupportTree->Pairs, i);
|
|
switch(This->Key)
|
|
{
|
|
case IDENT_ICON_TYPE: { Support->IconType = This->int64_t; } break;
|
|
case IDENT_ICON_VARIANTS: { Support->IconVariants = This->int64_t; } break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
if(Support->Icon.Length > 0)
|
|
{
|
|
if(Support->IconType == IT_DEFAULT_UNSET)
|
|
{
|
|
if(HasImageFileExtension(Support->Icon))
|
|
{
|
|
Support->IconType = IT_GRAPHICAL;
|
|
}
|
|
else
|
|
{
|
|
Support->IconType = IT_TEXTUAL;
|
|
}
|
|
}
|
|
|
|
switch(Support->IconType)
|
|
{
|
|
case IT_GRAPHICAL:
|
|
{
|
|
PushVariantUniquely(E, &V->VariantStrings, Support->Icon, Support->IconVariants, IDENT_ICON);
|
|
} break;
|
|
case IT_TEXTUAL:
|
|
{
|
|
if(Support->IconNormal.Length == 0) { Support->IconNormal = Support->Icon; }
|
|
if(Support->IconFocused.Length == 0) { Support->IconFocused = Support->Icon; }
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *PersonTree)
|
|
{
|
|
//PrintFunctionName("PushPersonOntoConfig()");
|
|
//PrintScopeTree(PersonTree);
|
|
person *This = MakeSpaceInBook(&C->Person);
|
|
This->ID = ResolveString(C, E, PersonTree, &PersonTree->ID, FALSE);
|
|
for(int i = 0; i < PersonTree->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *Pair = GetPlaceInBook(&PersonTree->Pairs, i);
|
|
if(IDENT_NAME == Pair->Key)
|
|
{
|
|
This->Name = ResolveString(C, E, PersonTree, Pair, FALSE);
|
|
}
|
|
else if(IDENT_HOMEPAGE == Pair->Key)
|
|
{
|
|
This->Homepage = ResolveString(C, E, PersonTree, Pair, FALSE);
|
|
}
|
|
else if(IDENT_QUOTE_USERNAME == Pair->Key)
|
|
{
|
|
This->QuoteUsername = ResolveString(C, E, PersonTree, Pair, FALSE);
|
|
}
|
|
}
|
|
|
|
if(This->QuoteUsername.Length == 0)
|
|
{
|
|
This->QuoteUsername = This->ID;
|
|
}
|
|
|
|
This->Support = InitBook(sizeof(support), 2);
|
|
for(int i = 0; i < PersonTree->Trees.ItemCount; ++i)
|
|
{
|
|
scope_tree *ThisSupport = GetPlaceInBook(&PersonTree->Trees, i);
|
|
PushSupport(C, E, V, This, ThisSupport);
|
|
}
|
|
}
|
|
|
|
void
|
|
PushRoleOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *RoleTree)
|
|
{
|
|
role *This = MakeSpaceInBook(&C->Role);
|
|
This->ID = ResolveString(C, E, RoleTree, &RoleTree->ID, FALSE);
|
|
for(int i = 0; i < RoleTree->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *Pair = GetPlaceInBook(&RoleTree->Pairs, i);
|
|
switch(Pair->Key)
|
|
{
|
|
case IDENT_NAME:
|
|
{
|
|
This->Name = ResolveString(C, E, RoleTree, Pair, FALSE);
|
|
} break;
|
|
case IDENT_PLURAL:
|
|
{
|
|
This->Plural = ResolveString(C, E, RoleTree, Pair, FALSE);
|
|
} break;
|
|
case IDENT_NON_SPEAKING:
|
|
{
|
|
This->NonSpeaking = Pair->bool;
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
PushCredit(config *C, resolution_errors *E, project *P, scope_tree *CreditTree)
|
|
{
|
|
CreditTree->ID.String = ResolveString(C, E, CreditTree, &CreditTree->ID, FALSE);
|
|
if(CreditTree->ID.String.Base)
|
|
{
|
|
person *Person = GetPerson(C, E, &CreditTree->ID);
|
|
if(Person)
|
|
{
|
|
for(int i = 0; i < CreditTree->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *This = GetPlaceInBook(&CreditTree->Pairs, i);
|
|
role *Role = GetRole(C, E, This);
|
|
if(Role)
|
|
{
|
|
credit *Credit = MakeSpaceInBook(&P->Credit);
|
|
// TODO(matt): Sort the P->Credit book by Person->SortName
|
|
Credit->Role = Role;
|
|
Credit->Person = Person;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PushProjectOntoProject(config *C, resolution_errors *E, config_verifiers *Verifiers, project *Parent, scope_tree *ProjectTree);
|
|
|
|
void
|
|
AddProjectToAssociation(config_string_association *A, project *P)
|
|
{
|
|
project **This = MakeSpaceInBook(&A->Projects);
|
|
*This = P;
|
|
}
|
|
|
|
void
|
|
PushAssociation(config_string_associations *HMMLDirs, string *S0, string *S1, project *P)
|
|
{
|
|
config_string_association *This = MakeSpaceInBook(&HMMLDirs->Associations);
|
|
This->Projects = InitBookOfPointers(4);
|
|
|
|
if(S0) { This->String0 = *S0; }
|
|
if(S1) { This->String1 = *S1; }
|
|
AddProjectToAssociation(This, P);
|
|
}
|
|
|
|
config_string_association *
|
|
GetAssociation(config_string_associations *Set, string String0, string String1)
|
|
{
|
|
config_string_association *Result = 0;
|
|
for(int i = 0; i < Set->Associations.ItemCount; ++i)
|
|
{
|
|
config_string_association *This = GetPlaceInBook(&Set->Associations, i);
|
|
if(StringsMatch(String0, This->String0) && StringsMatch(String1, This->String1))
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
config_string_association *
|
|
GetGlobalPathAssociation(config_string_associations *Set, string Path)
|
|
{
|
|
config_string_association *Result = 0;
|
|
for(int i = 0; i < Set->Associations.ItemCount; ++i)
|
|
{
|
|
config_string_association *This = GetPlaceInBook(&Set->Associations, i);
|
|
if(StringsMatchSized(Path, This->String0.Length, This->String0))
|
|
{
|
|
Path = TrimString(Path, This->String0.Length, 0);
|
|
if(Path.Length == 0 && This->String1.Length == 0)
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
else if(StringsMatchSized(Path, 1, Wrap0("/")))
|
|
{
|
|
Path = TrimString(Path, 1, 0);
|
|
if(Path.Length > 0 && StringsMatchSized(Path, This->String1.Length, This->String1))
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
SetUniqueHMMLDir(config *C, resolution_errors *E, config_string_associations *HMMLDirs, project *P, scope_tree *ProjectTree, config_pair *HMMLDir)
|
|
{
|
|
string Result = StripSlashes(ResolveString(C, E, ProjectTree, HMMLDir, TRUE), P_ABS);
|
|
bool Clashed = FALSE;
|
|
for(int i = 0; i < HMMLDirs->Associations.ItemCount; ++i)
|
|
{
|
|
config_string_association *This = GetPlaceInBook(&HMMLDirs->Associations, i);
|
|
if(StringsMatch(Result, This->String0))
|
|
{
|
|
AddProjectToAssociation(This, P);
|
|
PushError(E, S_ERROR, &HMMLDir->Position, IDENT_HMML_DIR);
|
|
Clashed = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!Clashed)
|
|
{
|
|
PushAssociation(HMMLDirs, &Result, 0, P);
|
|
}
|
|
P->HMMLDir = Result;
|
|
}
|
|
|
|
void
|
|
PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, scope_tree *ProjectTree)
|
|
{
|
|
P->Medium = InitBook(sizeof(medium), 8);
|
|
P->Child = InitBook(sizeof(project), 8);
|
|
P->Credit = InitBook(sizeof(credit), 4);
|
|
|
|
config_string_associations *HMMLDirs = &V->HMMLDirs;
|
|
P->ID = ResolveString(C, E, ProjectTree, &ProjectTree->ID, FALSE);
|
|
if(P->ID.Length > MAX_PROJECT_ID_LENGTH)
|
|
{
|
|
string Filepath = Wrap0(ProjectTree->ID.Position.Filename);
|
|
ConfigErrorSizing(&Filepath, ProjectTree->ID.Position.LineNumber, IDENT_PROJECT, &P->ID, MAX_PROJECT_ID_LENGTH);
|
|
PushError(E, S_ERROR, 0, IDENT_PROJECT);
|
|
}
|
|
|
|
ResetPen(&C->ResolvedVariables);
|
|
P->Lineage = DeriveLineageOfProject(C, ProjectTree);
|
|
|
|
// NOTE(matt): Initial pass over as-yet unresolvable local variable(s)
|
|
//
|
|
for(int i = 0; i < ProjectTree->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *This = GetPlaceInBook(&ProjectTree->Pairs, i);
|
|
switch(This->Key)
|
|
{
|
|
case IDENT_OWNER:
|
|
{
|
|
// TODO(matt): Do we need to fail completely if owner cannot be found?
|
|
P->Owner = GetPerson(C, E, This);
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
//
|
|
////
|
|
|
|
for(int i = 0; i < ProjectTree->Trees.ItemCount; ++i)
|
|
{
|
|
scope_tree *This = GetPlaceInBook(&ProjectTree->Trees, i);
|
|
switch(This->ID.Key)
|
|
{
|
|
case IDENT_MEDIUM:
|
|
{
|
|
PushMedium(C, E, V, P, This);
|
|
} break;
|
|
case IDENT_CREDIT:
|
|
{
|
|
PushCredit(C, E, P, This);
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < ProjectTree->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *This = GetPlaceInBook(&ProjectTree->Pairs, i);
|
|
string Filepath = Wrap0(This->Position.Filename);
|
|
switch(This->Key)
|
|
{
|
|
// NOTE(matt): String
|
|
case IDENT_DEFAULT_MEDIUM:
|
|
{ P->DefaultMedium = GetMediumFromProject(P, This->String); } break;
|
|
case IDENT_HMML_DIR:
|
|
{ SetUniqueHMMLDir(C, E, HMMLDirs, P, ProjectTree, This); } break;
|
|
case IDENT_PLAYER_TEMPLATE:
|
|
{ P->PlayerTemplatePath = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); } break;
|
|
case IDENT_THEME:
|
|
{ P->Theme = ResolveString(C, E, ProjectTree, This, FALSE);
|
|
if(P->Theme.Length > MAX_THEME_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key,
|
|
&P->Theme, MAX_THEME_LENGTH);
|
|
PushError(E, S_ERROR, 0, This->Key);
|
|
}
|
|
} break;
|
|
case IDENT_TITLE:
|
|
{
|
|
P->Title = ResolveString(C, E, ProjectTree, This, FALSE);
|
|
if(P->Title.Length > MAX_PROJECT_NAME_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key,
|
|
&P->Title, MAX_PROJECT_NAME_LENGTH);
|
|
PushError(E, S_ERROR, 0, This->Key);
|
|
}
|
|
} break;
|
|
case IDENT_HTML_TITLE: { P->HTMLTitle = ResolveString(C, E, ProjectTree, This, FALSE); } break;
|
|
// TODO(matt): Sort out this title list stuff
|
|
//case IDENT_TITLE_LIST_DELIMITER: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
|
|
//case IDENT_TITLE_LIST_PREFIX: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
|
|
//case IDENT_TITLE_LIST_SUFFIX: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
|
|
//case IDENT_TITLE_SUFFIX: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
|
|
case IDENT_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(&Filepath, This->Position.LineNumber, This->Key,
|
|
&P->BaseDir, MAX_BASE_DIR_LENGTH);
|
|
PushError(E, S_ERROR, 0, This->Key);
|
|
}
|
|
} break;
|
|
case IDENT_BASE_URL:
|
|
{
|
|
P->BaseURL = ResolveString(C, E, ProjectTree, This, FALSE);
|
|
if(P->BaseURL.Length > MAX_BASE_URL_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key,
|
|
&P->BaseURL, MAX_BASE_URL_LENGTH);
|
|
PushError(E, S_ERROR, 0, This->Key);
|
|
}
|
|
} break;
|
|
case IDENT_PLAYER_LOCATION:
|
|
{
|
|
P->PlayerLocation = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL);
|
|
if(P->PlayerLocation.Length > MAX_RELATIVE_PAGE_LOCATION_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key,
|
|
&P->PlayerLocation, MAX_RELATIVE_PAGE_LOCATION_LENGTH);
|
|
PushError(E, S_ERROR, 0, This->Key);
|
|
}
|
|
} break;
|
|
case IDENT_SEARCH_LOCATION:
|
|
{
|
|
P->SearchLocation = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL);
|
|
if(P->SearchLocation.Length > MAX_RELATIVE_PAGE_LOCATION_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key,
|
|
&P->SearchLocation, MAX_RELATIVE_PAGE_LOCATION_LENGTH);
|
|
PushError(E, S_ERROR, 0, This->Key);
|
|
}
|
|
} break;
|
|
case IDENT_SEARCH_TEMPLATE:
|
|
{ P->SearchTemplatePath = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); } break;
|
|
case IDENT_TEMPLATES_DIR:
|
|
{ P->TemplatesDir = StripSlashes(ResolveString(C, E, ProjectTree, This, TRUE), P_ABS); } break;
|
|
case IDENT_ART: { P->Art = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); } break;
|
|
case IDENT_ICON: { P->Icon = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); } break;
|
|
case IDENT_ICON_NORMAL: { P->IconNormal = ResolveString(C, E, ProjectTree, This, FALSE); } break;
|
|
case IDENT_ICON_FOCUSED: { P->IconFocused = ResolveString(C, E, ProjectTree, This, FALSE); } break;
|
|
case IDENT_ICON_DISABLED: { P->IconDisabled = ResolveString(C, E, ProjectTree, This, FALSE); } break;
|
|
|
|
// NOTE(matt): int64_t
|
|
case IDENT_GENRE: { P->Genre = This->int64_t; } break;
|
|
case IDENT_ART_VARIANTS: { P->ArtVariants = This->int64_t; } break;
|
|
case IDENT_ICON_TYPE: { P->IconType = This->int64_t; } break;
|
|
case IDENT_ICON_VARIANTS: { P->IconVariants = This->int64_t; } break;
|
|
case IDENT_NUMBERING_SCHEME: { P->NumberingScheme = This->int64_t; } break;
|
|
case IDENT_VOD_PLATFORM: { P->VODPlatform = This->int64_t; } break;
|
|
|
|
// NOTE(matt): bool
|
|
case IDENT_DENY_BESPOKE_TEMPLATES:
|
|
{ P->DenyBespokeTemplates = This->bool; } break;
|
|
case IDENT_IGNORE_PRIVACY:
|
|
{ P->IgnorePrivacy = This->bool; } break;
|
|
case IDENT_SINGLE_BROWSER_TAB:
|
|
{ P->SingleBrowserTab = This->bool; } break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < ProjectTree->Trees.ItemCount; ++i)
|
|
{
|
|
scope_tree *This = GetPlaceInBook(&ProjectTree->Trees, i);
|
|
if(This->ID.Key == IDENT_PROJECT)
|
|
{
|
|
PushProjectOntoProject(C, E, V, P, This);
|
|
}
|
|
}
|
|
|
|
config_string_association *BaseDirAndSearchLocation = GetAssociation(&V->BaseDirAndSearchLocation, P->BaseDir, P->SearchLocation);
|
|
if(BaseDirAndSearchLocation)
|
|
{
|
|
PushError(E, S_ERROR, 0, IDENT_SEARCH_LOCATION);
|
|
AddProjectToAssociation(BaseDirAndSearchLocation, P);
|
|
}
|
|
else
|
|
{
|
|
PushAssociation(&V->BaseDirAndSearchLocation, &P->BaseDir, &P->SearchLocation, P);
|
|
}
|
|
|
|
config_string_association *BaseDirAndPlayerLocation = GetAssociation(&V->BaseDirAndPlayerLocation, P->BaseDir, P->PlayerLocation);
|
|
if(BaseDirAndPlayerLocation)
|
|
{
|
|
PushError(E, S_ERROR, 0, IDENT_PLAYER_LOCATION);
|
|
AddProjectToAssociation(BaseDirAndPlayerLocation, P);
|
|
}
|
|
else
|
|
{
|
|
PushAssociation(&V->BaseDirAndPlayerLocation, &P->BaseDir, &P->PlayerLocation, P);
|
|
}
|
|
|
|
if(!P->DefaultMedium)
|
|
{
|
|
ResetPen(&C->ResolvedVariables);
|
|
ConfigError(0, 0, S_WARNING, "Unset default_medium for project: ", &P->Lineage);
|
|
PushError(E, S_WARNING, 0, IDENT_DEFAULT_MEDIUM);
|
|
}
|
|
|
|
if(P->Art.Length > 0)
|
|
{
|
|
PushVariantUniquely(E, &V->VariantStrings, P->Art, P->ArtVariants, IDENT_ART);
|
|
}
|
|
|
|
if(P->Icon.Length > 0)
|
|
{
|
|
if(P->IconType == IT_DEFAULT_UNSET)
|
|
{
|
|
if(HasImageFileExtension(P->Icon))
|
|
{
|
|
P->IconType = IT_GRAPHICAL;
|
|
}
|
|
else
|
|
{
|
|
P->IconType = IT_TEXTUAL;
|
|
}
|
|
}
|
|
|
|
switch(P->IconType)
|
|
{
|
|
case IT_GRAPHICAL:
|
|
{
|
|
PushVariantUniquely(E, &V->VariantStrings, P->Icon, P->IconVariants, IDENT_ICON);
|
|
} break;
|
|
case IT_TEXTUAL:
|
|
{
|
|
if(P->IconNormal.Length == 0) { P->IconNormal = P->Icon; }
|
|
if(P->IconFocused.Length == 0) { P->IconFocused = P->Icon; }
|
|
if(P->IconDisabled.Length == 0) { P->IconDisabled = P->Icon; }
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
C->RespectingPrivacy |= !P->IgnorePrivacy;
|
|
}
|
|
|
|
void
|
|
PushProjectOntoProject(config *C, resolution_errors *E, config_verifiers *Verifiers, project *Parent, scope_tree *ProjectTree)
|
|
{
|
|
project *P = MakeSpaceInBook(&Parent->Child);
|
|
|
|
PushProject(C, E, Verifiers, P, ProjectTree);
|
|
P->Parent = Parent;
|
|
}
|
|
|
|
project *
|
|
GetProjectFromProject(project *Parent, string ID)
|
|
{
|
|
project *Result = 0;
|
|
if(Parent)
|
|
{
|
|
for(int i = 0; i < Parent->Child.ItemCount; ++i)
|
|
{
|
|
project *This = GetPlaceInBook(&Parent->Child, i);
|
|
if(StringsMatch(ID, This->ID))
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
PushProjectOntoConfig(config *C, resolution_errors *E, config_verifiers *Verifiers, scope_tree *ProjectTree)
|
|
{
|
|
project *P = MakeSpaceInBook(&C->Project);
|
|
|
|
PushProject(C, E, Verifiers, P, ProjectTree);
|
|
}
|
|
|
|
project *
|
|
GetProjectFromConfig(config *C, string ID)
|
|
{
|
|
project *Result = 0;
|
|
if(C)
|
|
{
|
|
for(int i = 0; i < C->Project.ItemCount; ++i)
|
|
{
|
|
project *This = GetPlaceInBook(&C->Project, i);
|
|
if(StringsMatch(ID, This->ID))
|
|
{
|
|
Result = This;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
PrintAssociationClashes(config_string_associations *A)
|
|
{
|
|
for(int i = 0; i < A->Associations.ItemCount; ++i)
|
|
{
|
|
config_string_association *Association = GetPlaceInBook(&A->Associations, i);
|
|
if(Association->Projects.ItemCount > 1)
|
|
{
|
|
ConfigError(0, 0, S_ERROR, "Multiple projects share the same:", 0);
|
|
|
|
config_pair Assoc0 = { .Key = A->ID0, .String = Association->String0, .Type = PT_STRING };
|
|
int IndentLevel = 2;
|
|
PrintPair(&Assoc0, ": ", FALSE, IndentLevel, FALSE, FALSE);
|
|
if(A->ID1 != IDENT_NULL)
|
|
{
|
|
config_pair Assoc1 = { .Key = A->ID1, .String = Association->String1, .Type = PT_STRING };
|
|
PrintPair(&Assoc1, ": ", FALSE, IndentLevel, TRUE, FALSE);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
for(int j = 0; j < Association->Projects.ItemCount; ++j)
|
|
{
|
|
project **P = GetPlaceInBook(&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(memory_book *VariantStrings)
|
|
{
|
|
for(int i = 0; i < VariantStrings->ItemCount; ++i)
|
|
{
|
|
variant_string *ThisVariantsString = GetPlaceInBook(VariantStrings, i);
|
|
if(ThisVariantsString->Variants.ItemCount > 1)
|
|
{
|
|
ConfigError(0, 0, S_ERROR, "Sprite has inconsistently set variants ", &ThisVariantsString->Path);
|
|
for(int j = 0; j < ThisVariantsString->Variants.ItemCount; ++j)
|
|
{
|
|
string Margin = Wrap0(" ");
|
|
variant V = *(variant *)GetPlaceInBook(&ThisVariantsString->Variants, j);
|
|
PrintVariants(V, j % 2 ? CS_CYAN_BOLD : CS_YELLOW_BOLD, Margin);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
FreeAssociations(config_string_associations *A)
|
|
{
|
|
for(int i = 0; i < A->Associations.ItemCount; ++i)
|
|
{
|
|
config_string_association *This = GetPlaceInBook(&A->Associations, i);
|
|
FreeBook(&This->Projects);
|
|
}
|
|
FreeBook(&A->Associations);
|
|
}
|
|
|
|
void
|
|
FreeVariants(memory_book *VariantStrings)
|
|
{
|
|
for(int i = 0; i < VariantStrings->ItemCount; ++i)
|
|
{
|
|
variant_string *This = GetPlaceInBook(VariantStrings, i);
|
|
FreeBook(&This->Variants);
|
|
}
|
|
FreeBook(VariantStrings);
|
|
}
|
|
|
|
void
|
|
FreeVerifiers(config_verifiers *Verifiers)
|
|
{
|
|
FreeAssociations(&Verifiers->BaseDirAndSearchLocation);
|
|
FreeAssociations(&Verifiers->BaseDirAndPlayerLocation);
|
|
FreeAssociations(&Verifiers->HMMLDirs);
|
|
FreeVariants(&Verifiers->VariantStrings);
|
|
}
|
|
|
|
void
|
|
PrintLogLevel(char *Title, log_level Level, char *Delimiter, uint64_t Indentation, bool AppendNewline)
|
|
{
|
|
Indent(Indentation);
|
|
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_LOG_LEVEL].String);
|
|
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)
|
|
{
|
|
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, .String = S->ID, .Type = PT_STRING };
|
|
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, .String = S->URL, .Type = PT_STRING };
|
|
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, .String = S->Icon, .Type = PT_STRING };
|
|
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, .String = S->IconNormal, .Type = PT_STRING };
|
|
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, .String = S->IconFocused, .Type = PT_STRING };
|
|
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, 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, .String = P->Name, .Type = PT_STRING }; 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, .String = P->Homepage, .Type = PT_STRING }; PrintPair(&Homepage, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
|
|
--RowsRequired;
|
|
}
|
|
|
|
for(int i = 0; i < P->Support.ItemCount; ++i)
|
|
{
|
|
PrintSupport(GetPlaceInBook(&P->Support, i), Typography, IndentationLevel, &RowsRequired);
|
|
}
|
|
}
|
|
|
|
uint8_t
|
|
GetRowsRequiredForRoleInfo(role *R)
|
|
{
|
|
uint8_t RowsRequired = 0;
|
|
if(R->Name.Length > 0) { ++RowsRequired; }
|
|
if(R->Plural.Length > 0) { ++RowsRequired; }
|
|
int NonSpeakingLines = 1;
|
|
RowsRequired += NonSpeakingLines;
|
|
return RowsRequired;
|
|
}
|
|
|
|
void
|
|
PrintRole(role *R, typography *Typography)
|
|
{
|
|
bool HaveInfo = R->Name.Length > 0 || R->Plural.Length > 0;
|
|
fprintf(stderr, "\n"
|
|
"%s%s%s%s%s ", HaveInfo ? Typography->UpperLeftCorner : Typography->UpperLeft,
|
|
Typography->Horizontal, Typography->Horizontal, Typography->Horizontal, Typography->UpperRight);
|
|
PrintStringC(CS_GREEN_BOLD, R->ID);
|
|
fprintf(stderr, "\n");
|
|
|
|
uint8_t RowsRequired = GetRowsRequiredForRoleInfo(R);
|
|
|
|
int IndentationLevel = 0;
|
|
bool ShouldFillSyntax = FALSE;
|
|
if(R->Name.Length > 0)
|
|
{
|
|
fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
|
|
config_pair Name = { .Key = IDENT_NAME, .String = R->Name, .Type = PT_STRING }; PrintPair(&Name, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
|
|
--RowsRequired;
|
|
}
|
|
|
|
if(R->Plural.Length > 0)
|
|
{
|
|
fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
|
|
config_pair Plural = { .Key = IDENT_PLURAL, .String = R->Plural, .Type = PT_STRING }; PrintPair(&Plural, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
|
|
--RowsRequired;
|
|
}
|
|
fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
|
|
config_pair NonSpeaking = { .Key = IDENT_NON_SPEAKING, .bool = R->NonSpeaking, .Type = PT_BOOL }; PrintPair(&NonSpeaking, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE);
|
|
--RowsRequired;
|
|
}
|
|
|
|
void
|
|
PrintLineage(string Lineage, bool AppendNewline)
|
|
{
|
|
string Parent = StripComponentFromPath(Lineage);
|
|
PrintStringC(CS_BLACK_BOLD, Parent);
|
|
if(Parent.Length > 0)
|
|
{
|
|
PrintC(CS_BLACK_BOLD, "/");
|
|
}
|
|
string Us = GetFinalComponent(Lineage);
|
|
PrintStringC(CS_BLUE_BOLD, Us);
|
|
if(AppendNewline) { fprintf(stderr, "\n"); }
|
|
}
|
|
|
|
uint8_t
|
|
GetColumnsRequiredForMedium(medium *M, typography *T)
|
|
{
|
|
uint8_t Result = M->ID.Length + StringLength(T->Delimiter) + M->Name.Length + sizeof(" (")-1 + M->Icon.Length + sizeof(")")-1;
|
|
if(M->Hidden) { Result += sizeof(" [hidden]")-1; }
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
PrintTitle(char *Text, typography *T, uint8_t Generation, int AvailableColumns,
|
|
int64_t SectionItemCount /* NOTE(matt): Use any non-0 when the section is known to have content */)
|
|
{
|
|
char *LeftmostChar = "╾";
|
|
char *InnerChar = "─";
|
|
char *RightmostChar = "╼";
|
|
|
|
int LeftmostCharLength = 1;
|
|
int SpacesRequired = 2;
|
|
int RightmostCharLength = 1;
|
|
int InnerCharsRequired = AvailableColumns - LeftmostCharLength - StringLength(Text) - RightmostCharLength - SpacesRequired;
|
|
int LeftCharsRequired = 4;
|
|
int RightCharsRequired = InnerCharsRequired - LeftCharsRequired;
|
|
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);
|
|
if(SectionItemCount == 0)
|
|
{
|
|
//fprintf(stderr, "\n");
|
|
CarriageReturn(T, Generation);
|
|
}
|
|
}
|
|
|
|
void
|
|
PrintMedia(project *P, typography *T, uint8_t Generation, int AvailableColumns)
|
|
{
|
|
int IndentationLevel = 0;
|
|
CarriageReturn(T, Generation);
|
|
fprintf(stderr, "%s", T->Margin);
|
|
PrintTitle("Media", T, Generation, AvailableColumns, P->Medium.ItemCount);
|
|
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
|
|
PrintCredits(config *C, project *P, typography *T, uint8_t Generation, int IndentationLevel, int AvailableColumns)
|
|
{
|
|
for(int i = 0; i < C->Role.ItemCount; ++i)
|
|
{
|
|
role *Role = GetPlaceInBook(&C->Role, i);
|
|
int CreditCount = 0;
|
|
for(int j = 0; j < P->Credit.ItemCount; ++j)
|
|
{
|
|
credit *Credit = GetPlaceInBook(&P->Credit, j);
|
|
if(Role == Credit->Role)
|
|
{
|
|
++CreditCount;
|
|
}
|
|
}
|
|
|
|
if(CreditCount > 0)
|
|
{
|
|
CarriageReturn(T, Generation);
|
|
int Alignment = 0;
|
|
Alignment += fprintf(stderr, "%s", T->Margin);
|
|
if(CreditCount == 1)
|
|
{
|
|
Alignment += PrintStringC(CS_YELLOW_BOLD, Role->Name.Length ? Role->Name : Role->ID);
|
|
}
|
|
else
|
|
{
|
|
if(Role->Plural.Length > 0)
|
|
{
|
|
Alignment += PrintStringC(CS_YELLOW_BOLD, Role->Plural);
|
|
}
|
|
else
|
|
{
|
|
Alignment += PrintStringC(CS_YELLOW_BOLD, Role->Name.Length ? Role->Name : Role->ID);
|
|
Alignment += PrintStringC(CS_YELLOW_BOLD, Wrap0("s"));
|
|
}
|
|
}
|
|
Alignment += fprintf(stderr, "%s", T->Delimiter);
|
|
bool Printed = FALSE;
|
|
for(int j = 0; j < P->Credit.ItemCount; ++j)
|
|
{
|
|
credit *Credit = GetPlaceInBook(&P->Credit, j);
|
|
if(Role == Credit->Role)
|
|
{
|
|
if(Printed)
|
|
{
|
|
CarriageReturn(T, Generation);
|
|
AlignText(Alignment);
|
|
}
|
|
PrintStringC(CS_GREEN_BOLD, Credit->Person->Name.Length ? Credit->Person->Name : Credit->Person->ID);
|
|
Printed = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TypesetPair(typography *T, uint8_t Generation, config_identifier_id Key, string Value, int AvailableColumns)
|
|
{
|
|
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_pair Pair = { .Key = Key, .bool = Value, .Type = PT_BOOL };
|
|
bool ShouldFillSyntax = FALSE;
|
|
PrintPair(&Pair, T->Delimiter, ShouldFillSyntax, 0, FALSE, FALSE);
|
|
}
|
|
|
|
void
|
|
TypesetGenre(typography *T, uint8_t Generation, genre G)
|
|
{
|
|
CarriageReturn(T, Generation);
|
|
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
|
|
TypesetVODPlatform(typography *T, uint8_t Generation, vod_platform P)
|
|
{
|
|
CarriageReturn(T, Generation);
|
|
fprintf(stderr, "%s", T->Margin);
|
|
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_VOD_PLATFORM].String);
|
|
fprintf(stderr, "%s", T->Delimiter);
|
|
if(IsValidVODPlatform(P))
|
|
{
|
|
PrintC(CS_GREEN_BOLD, VODPlatformStrings[P]);
|
|
}
|
|
else
|
|
{
|
|
PrintC(CS_YELLOW, "[unset]");
|
|
}
|
|
}
|
|
|
|
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(config *C, 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", T, Generation, AvailableColumns, -1);
|
|
|
|
TypesetPair(T, Generation, IDENT_TITLE, P->Title, AvailableColumns);
|
|
TypesetPair(T, Generation, IDENT_HTML_TITLE, P->HTMLTitle, AvailableColumns);
|
|
TypesetPair(T, Generation, IDENT_DEFAULT_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_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);
|
|
TypesetVODPlatform(T, Generation, P->VODPlatform);
|
|
TypesetNumberingScheme(T, Generation, P->NumberingScheme);
|
|
TypesetBool(T, Generation, IDENT_IGNORE_PRIVACY, P->IgnorePrivacy);
|
|
TypesetBool(T, Generation, IDENT_SINGLE_BROWSER_TAB, P->SingleBrowserTab);
|
|
|
|
TypesetPair(T, Generation, IDENT_OWNER, P->Owner ? P->Owner->ID : Wrap0(""), AvailableColumns);
|
|
|
|
CarriageReturn(T, Generation);
|
|
CarriageReturn(T, Generation);
|
|
fprintf(stderr, "%s", T->Margin);
|
|
PrintTitle("Credits", T, Generation, AvailableColumns, P->Credit.ItemCount);
|
|
if(P->Credit.ItemCount > 0)
|
|
{
|
|
PrintCredits(C, P, T, Generation, IndentationLevel, AvailableColumns);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s", T->Margin);
|
|
PrintC(CS_YELLOW, "[none]");
|
|
}
|
|
|
|
if(P->Child.ItemCount)
|
|
{
|
|
CarriageReturn(T, Generation);
|
|
CarriageReturn(T, Generation);
|
|
fprintf(stderr, "%s", T->Margin);
|
|
PrintTitle("Children", T, Generation, AvailableColumns, -1);
|
|
++Ancestors;
|
|
for(int i = 0; i < P->Child.ItemCount; ++i)
|
|
{
|
|
PrintProject(C, GetPlaceInBook(&P->Child, i), T, Ancestors, IndentationLevel, TerminalColumns);
|
|
}
|
|
--Ancestors;
|
|
}
|
|
CarriageReturn(T, Ancestors);
|
|
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", &Typography, 0, TermCols, -1);
|
|
int IndentationLevel = 0;
|
|
|
|
int AvailableColumns = TermCols - StringLength(Typography.Margin);
|
|
TypesetPair(&Typography, 0, IDENT_DB_LOCATION, C->DatabaseLocation, AvailableColumns);
|
|
TypesetPair(&Typography, 0, IDENT_CACHE_DIR, C->CacheDir, AvailableColumns);
|
|
|
|
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_pair PrivacyCheckInterval = { .Key = IDENT_PRIVACY_CHECK_INTERVAL, .int64_t = C->PrivacyCheckInterval / SecondsPerMinute, .Type = PT_INT64 }; PrintPair(&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", &Typography, 0, TermCols, C->Person.ItemCount);
|
|
for(int i = 0; i < C->Person.ItemCount; ++i)
|
|
{
|
|
PrintPerson(GetPlaceInBook(&C->Person, i), &Typography);
|
|
}
|
|
|
|
fprintf(stderr, "\n");
|
|
PrintTitle("Roles", &Typography, 0, TermCols, C->Role.ItemCount);
|
|
for(int i = 0; i < C->Role.ItemCount; ++i)
|
|
{
|
|
PrintRole(GetPlaceInBook(&C->Role, i), &Typography);
|
|
}
|
|
|
|
fprintf(stderr, "\n");
|
|
PrintTitle("Projects", &Typography, 0, TermCols, -1);
|
|
for(int i = 0; i < C->Project.ItemCount; ++i)
|
|
{
|
|
PrintProject(C, GetPlaceInBook(&C->Project, i), &Typography, 0, IndentationLevel, TermCols);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
FreeProject(project *P)
|
|
{
|
|
FreeBook(&P->Medium);
|
|
FreeBook(&P->Credit);
|
|
for(int i = 0; i < P->Child.ItemCount; ++i)
|
|
{
|
|
FreeProject(GetPlaceInBook(&P->Child, i));
|
|
}
|
|
FreeBook(&P->Child);
|
|
FreeTemplate(&P->SearchTemplate);
|
|
FreeTemplate(&P->PlayerTemplate);
|
|
}
|
|
|
|
// NOTE(matt): Making this a #define permits Free() to null out the incoming C
|
|
#define FreeConfig(C)\
|
|
{\
|
|
if(C)\
|
|
{\
|
|
for(int i = 0; i < C->Person.ItemCount; ++i)\
|
|
{\
|
|
person *Person = GetPlaceInBook(&C->Person, i);\
|
|
FreeBook(&Person->Support);\
|
|
}\
|
|
FreeBook(&C->Person);\
|
|
FreeBook(&C->Role);\
|
|
for(int i = 0; i < C->Project.ItemCount; ++i)\
|
|
{\
|
|
FreeProject(GetPlaceInBook(&C->Project, i));\
|
|
}\
|
|
FreeBook(&C->Project);\
|
|
FreeBook(&C->ResolvedVariables);\
|
|
FreeTemplate(&C->SearchTemplate);\
|
|
\
|
|
Free(C);\
|
|
}\
|
|
}\
|
|
|
|
config_verifiers
|
|
InitVerifiers(void)
|
|
{
|
|
config_verifiers Result = {};
|
|
Result.VariantStrings = InitBook(sizeof(variant_string), 8);
|
|
|
|
Result.HMMLDirs.ID0 = IDENT_HMML_DIR;
|
|
Result.HMMLDirs.Associations = InitBook(sizeof(config_string_association), 8);
|
|
|
|
Result.BaseDirAndSearchLocation.ID0 = IDENT_BASE_DIR;
|
|
Result.BaseDirAndSearchLocation.ID1 = IDENT_SEARCH_LOCATION;
|
|
Result.BaseDirAndSearchLocation.Associations = InitBook(sizeof(config_string_association), 8);
|
|
|
|
Result.BaseDirAndPlayerLocation.ID0 = IDENT_BASE_DIR;
|
|
Result.BaseDirAndPlayerLocation.ID1 = IDENT_PLAYER_LOCATION;
|
|
Result.BaseDirAndPlayerLocation.Associations = InitBook(sizeof(config_string_association), 8);
|
|
return Result;
|
|
}
|
|
|
|
bool
|
|
IsPositioned(config_pair *Position)
|
|
{
|
|
return Position && Position->int64_t != 0;
|
|
}
|
|
|
|
void
|
|
PositionRolesInConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *S)
|
|
{
|
|
memory_book RolesStaging = InitBookOfPointers(4);
|
|
for(int i = 0; i < S->Trees.ItemCount; ++i)
|
|
{
|
|
scope_tree *Tree = GetPlaceInBook(&S->Trees, i);
|
|
if(Tree->ID.Key == IDENT_ROLE)
|
|
{
|
|
scope_tree **This = MakeSpaceInBook(&RolesStaging);
|
|
*This = Tree;
|
|
}
|
|
}
|
|
|
|
int SlotCount = RolesStaging.ItemCount;
|
|
scope_tree *RoleSlot[SlotCount];
|
|
Clear(RoleSlot, sizeof(scope_tree *) * SlotCount);
|
|
for(int i = 0; i < SlotCount; ++i)
|
|
{
|
|
scope_tree **Role = GetPlaceInBook(&RolesStaging, i);
|
|
config_pair *Position = GetPair(*Role, IDENT_POSITION);
|
|
if(IsPositioned(Position))
|
|
{
|
|
int TargetPos;
|
|
if(Position->int64_t < 0)
|
|
{
|
|
TargetPos = SlotCount + Position->int64_t;
|
|
Clamp(0, TargetPos, SlotCount - 1);
|
|
while(RoleSlot[TargetPos] && TargetPos > 0)
|
|
{
|
|
--TargetPos;
|
|
}
|
|
|
|
while(RoleSlot[TargetPos] && TargetPos < SlotCount - 1)
|
|
{
|
|
++TargetPos;
|
|
}
|
|
}
|
|
else if(Position->int64_t > 0)
|
|
{
|
|
TargetPos = Position->int64_t - 1;
|
|
Clamp(0, TargetPos, SlotCount - 1);
|
|
while(RoleSlot[TargetPos] && TargetPos < SlotCount - 1)
|
|
{
|
|
++TargetPos;
|
|
}
|
|
|
|
while(RoleSlot[TargetPos] && TargetPos > 0)
|
|
{
|
|
--TargetPos;
|
|
}
|
|
}
|
|
RoleSlot[TargetPos] = *Role;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < SlotCount; ++i)
|
|
{
|
|
scope_tree **Role = GetPlaceInBook(&RolesStaging, i);
|
|
config_pair *Position = GetPair(*Role, IDENT_POSITION);
|
|
if(!IsPositioned(Position))
|
|
{
|
|
for(int j = 0; j < SlotCount; ++j)
|
|
{
|
|
if(!RoleSlot[j])
|
|
{
|
|
RoleSlot[j] = *Role;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < SlotCount; ++i)
|
|
{
|
|
PushRoleOntoConfig(C, E, V, RoleSlot[i]);
|
|
}
|
|
|
|
FreeBook(&RolesStaging);
|
|
}
|
|
|
|
config *
|
|
ResolveVariables(scope_tree *S)
|
|
{
|
|
Assert(LOG_COUNT == ArrayCount(LogLevelStrings));
|
|
config *Result = calloc(1, sizeof(config));
|
|
Result->ResolvedVariables = InitBookOfStrings(Kilobytes(1));
|
|
Result->Person = InitBook(sizeof(person), 8);
|
|
Result->Role = InitBook(sizeof(role), 4);
|
|
Result->Project = InitBook(sizeof(project), 8);
|
|
|
|
resolution_errors Errors = {};
|
|
Errors.Warnings = InitBook(sizeof(resolution_error), 16);
|
|
Errors.Errors = InitBook(sizeof(resolution_error), 16);
|
|
|
|
config_verifiers Verifiers = InitVerifiers();
|
|
|
|
for(int i = 0; i < S->Pairs.ItemCount; ++i)
|
|
{
|
|
config_pair *Pair = GetPlaceInBook(&S->Pairs, i);
|
|
string Filepath = Wrap0(Pair->Position.Filename);
|
|
switch(Pair->Key)
|
|
{
|
|
// NOTE(matt): String
|
|
case IDENT_DB_LOCATION:
|
|
{ Result->DatabaseLocation = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS); } break;
|
|
case IDENT_GLOBAL_SEARCH_DIR:
|
|
{
|
|
Result->GlobalSearchDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS);
|
|
if(Result->GlobalSearchDir.Length > MAX_BASE_DIR_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
|
|
&Result->GlobalSearchDir, MAX_BASE_DIR_LENGTH);
|
|
PushError(&Errors, S_ERROR, 0, Pair->Key);
|
|
}
|
|
} break;
|
|
case IDENT_GLOBAL_SEARCH_URL:
|
|
{
|
|
Result->GlobalSearchURL = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS);
|
|
if(Result->GlobalSearchURL.Length > MAX_BASE_URL_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
|
|
&Result->GlobalSearchURL, MAX_BASE_URL_LENGTH);
|
|
PushError(&Errors, S_ERROR, 0, Pair->Key);
|
|
}
|
|
} break;
|
|
case IDENT_GLOBAL_TEMPLATES_DIR:
|
|
{ Result->GlobalTemplatesDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS); } break;
|
|
case IDENT_GLOBAL_SEARCH_TEMPLATE:
|
|
{ Result->GlobalSearchTemplatePath = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL); } break;
|
|
case IDENT_GLOBAL_THEME:
|
|
{
|
|
Result->GlobalTheme = ResolveString(Result, &Errors, S, Pair, FALSE);
|
|
if(Result->GlobalTheme.Length > MAX_THEME_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
|
|
&Result->GlobalTheme, MAX_THEME_LENGTH);
|
|
PushError(&Errors, S_ERROR, 0, Pair->Key);
|
|
}
|
|
} break;
|
|
case IDENT_ASSETS_ROOT_DIR:
|
|
{
|
|
Result->AssetsRootDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS);
|
|
if(Result->AssetsRootDir.Length > MAX_ROOT_DIR_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
|
|
&Result->AssetsRootDir, MAX_ROOT_DIR_LENGTH);
|
|
PushError(&Errors, S_ERROR, 0, Pair->Key);
|
|
}
|
|
} break;
|
|
case IDENT_ASSETS_ROOT_URL:
|
|
{
|
|
Result->AssetsRootURL = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS);
|
|
if(Result->AssetsRootURL.Length > MAX_ROOT_URL_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
|
|
&Result->AssetsRootURL, MAX_ROOT_URL_LENGTH);
|
|
PushError(&Errors, S_ERROR, 0, Pair->Key);
|
|
}
|
|
} break;
|
|
case IDENT_CSS_PATH:
|
|
{
|
|
Result->CSSDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL);
|
|
if(Result->CSSDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
|
|
&Result->CSSDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH);
|
|
PushError(&Errors, S_ERROR, 0, Pair->Key);
|
|
}
|
|
} break;
|
|
case IDENT_IMAGES_PATH:
|
|
{
|
|
Result->ImagesDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL);
|
|
if(Result->ImagesDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
|
|
&Result->ImagesDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH);
|
|
PushError(&Errors, S_ERROR, 0, Pair->Key);
|
|
}
|
|
} break;
|
|
case IDENT_JS_PATH:
|
|
{
|
|
Result->JSDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL);
|
|
if(Result->JSDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH)
|
|
{
|
|
ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key,
|
|
&Result->JSDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH);
|
|
PushError(&Errors, S_ERROR, 0, Pair->Key);
|
|
}
|
|
} break;
|
|
case IDENT_QUERY_STRING:
|
|
{ Result->QueryString = ResolveString(Result, &Errors, S, Pair, FALSE); } break;
|
|
case IDENT_CACHE_DIR:
|
|
{ Result->CacheDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS); } break;
|
|
|
|
// NOTE(matt): int64_t
|
|
case IDENT_PRIVACY_CHECK_INTERVAL:
|
|
{ Result->PrivacyCheckInterval = SECONDS_PER_MINUTE * Pair->int64_t; } break;
|
|
case IDENT_LOG_LEVEL:
|
|
{ Result->LogLevel = Pair->int64_t; } break;
|
|
|
|
// NOTE(matt): bool
|
|
case IDENT_SUPPRESS_PROMPTS:
|
|
{ Result->SuppressingPrompts = Pair->bool; } break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < S->Trees.ItemCount; ++i)
|
|
{
|
|
scope_tree *Tree = GetPlaceInBook(&S->Trees, i);
|
|
if(Tree->ID.Key == IDENT_PERSON)
|
|
{
|
|
PushPersonOntoConfig(Result, &Errors, &Verifiers, Tree);
|
|
}
|
|
}
|
|
|
|
PositionRolesInConfig(Result, &Errors, &Verifiers, S);
|
|
|
|
for(int i = 0; i < S->Trees.ItemCount; ++i)
|
|
{
|
|
scope_tree *Tree = GetPlaceInBook(&S->Trees, i);
|
|
if(Tree->ID.Key == IDENT_PROJECT)
|
|
{
|
|
PushProjectOntoConfig(Result, &Errors, &Verifiers, Tree);
|
|
}
|
|
}
|
|
|
|
config_string_association *BaseDirAndSearchLocation = GetGlobalPathAssociation(&Verifiers.BaseDirAndSearchLocation, Result->GlobalSearchDir);
|
|
if(BaseDirAndSearchLocation)
|
|
{
|
|
PushError(&Errors, S_ERROR, 0, IDENT_GLOBAL_SEARCH_DIR);
|
|
AddProjectToAssociation(BaseDirAndSearchLocation, 0);
|
|
}
|
|
|
|
// TODO(matt): Mandate that, if a GlobalSearchDir or GlobalSearchURL is set, then both must be set
|
|
if(!Result->GlobalTheme.Length)
|
|
{
|
|
ConfigErrorUnset(IDENT_GLOBAL_THEME);
|
|
PushError(&Errors, S_ERROR, 0, IDENT_GLOBAL_THEME);
|
|
}
|
|
|
|
if(Errors.Errors.ItemCount > 0)
|
|
{
|
|
PrintAssociationClashes(&Verifiers.HMMLDirs);
|
|
PrintAssociationClashes(&Verifiers.BaseDirAndSearchLocation);
|
|
PrintAssociationClashes(&Verifiers.BaseDirAndPlayerLocation);
|
|
PrintVariantInconsistencies(&Verifiers.VariantStrings);
|
|
FreeConfig(Result);
|
|
}
|
|
|
|
FreeVerifiers(&Verifiers);
|
|
FreeErrors(&Errors);
|
|
|
|
return Result;
|
|
}
|
|
|
|
config *
|
|
ParseConfig(string Path, memory_book *TokensList)
|
|
{
|
|
//PrintFunctionName("ParseConfig()");
|
|
_memory_book(config_type_spec) TypeSpecs = InitTypeSpecs();
|
|
|
|
#if 0
|
|
MEM_LOOP_PRE_FREE("InitTypeSpecs")
|
|
FreeTypeSpecs(&TypeSpecs);
|
|
MEM_LOOP_PRE_WORK()
|
|
TypeSpecs = InitTypeSpecs();
|
|
MEM_LOOP_POST("InitTypeSpecs")
|
|
#endif
|
|
config *Result = 0;
|
|
tokens *T = Tokenise(TokensList, Path);
|
|
|
|
#if 0
|
|
MEM_LOOP_PRE_FREE("Tokenise")
|
|
FreeTokensList(TokensList);
|
|
//FreeAndResetCount(WatchHandles.Handle, WatchHandles.Count);
|
|
MEM_LOOP_PRE_WORK()
|
|
T = Tokenise(TokensList, Path);
|
|
MEM_LOOP_POST("Tokenise")
|
|
#endif
|
|
|
|
if(T)
|
|
{
|
|
scope_tree *ScopeTree = InitRootScopeTree();
|
|
SetTypeSpec(ScopeTree, &TypeSpecs);
|
|
SetDefaults(ScopeTree, &TypeSpecs);
|
|
ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, 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 = InitRootScopeTree();
|
|
SetTypeSpec(ScopeTree, &TypeSpecs);
|
|
SetDefaults(ScopeTree, &TypeSpecs);
|
|
ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, 0);
|
|
MEM_LOOP_POST("ScopeTree")
|
|
#endif
|
|
//
|
|
////
|
|
|
|
//PrintScopeTree(ScopeTree);
|
|
if(ScopeTree)
|
|
{
|
|
//PrintC(CS_GREEN, "Resolving scope tree\n");
|
|
Result = ResolveVariables(ScopeTree);
|
|
|
|
#if 0
|
|
MEM_LOOP_PRE_FREE("ResolveVariables")
|
|
FreeConfig(Result);
|
|
MEM_LOOP_PRE_WORK()
|
|
Result = ResolveVariables(ScopeTree);
|
|
MEM_LOOP_POST("ResolveVariables")
|
|
#endif
|
|
|
|
FreeScopeTree(ScopeTree);
|
|
}
|
|
FreeTokensList(TokensList);
|
|
}
|
|
FreeTypeSpecs(&TypeSpecs);
|
|
return Result;
|
|
}
|
|
//
|
|
// config
|
|
|
|
#if 0
|
|
int
|
|
main(int ArgC, char **Args)
|
|
{
|
|
//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
|