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