diff --git a/cinera/cinera.c b/cinera/cinera.c index 61e8272..fbceb7c 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -23,7 +23,7 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, .Minor = 7, - .Patch = 9 + .Patch = 10 }; #include // NOTE(matt): varargs @@ -907,6 +907,18 @@ StringsMatch(string A, string B) return FALSE; } +bool +StringsMatchSized(string A, int Size, string B) +{ + int i = 0; + while(i < A.Length && i < B.Length && A.Base[i] == B.Base[i]) { ++i; } + if(i == Size) + { + return TRUE; + } + return FALSE; +} + bool StringsMatchCaseInsensitive(string A, string B) { @@ -2723,6 +2735,15 @@ GetBaseFilename(string Filepath, return Result; } +string +TrimString(string S, uint32_t CharsFromStart, uint32_t CharsFromEnd) +{ + string Result = S; + Result.Length -= (CharsFromStart + CharsFromEnd); + Result.Base += CharsFromStart; + return Result; +} + char *WatchTypeStrings[] = { "WT_HMML", @@ -12351,16 +12372,6 @@ DeleteFromDB(neighbourhood *N, string BaseFilename) return Entry ? RC_SUCCESS : RC_NOOP; } - -string -TrimString(string S, uint32_t CharsFromStart, uint32_t CharsFromEnd) -{ - string Result = S; - Result.Length -= (CharsFromStart + CharsFromEnd); - Result.Base += CharsFromStart; - return Result; -} - void GenerateFilterOfProjectAndChildren(buffer *Filter, db_header_project *StoredP, project *P, bool *SearchRequired, bool TopLevel, bool *RequiresCineraJS) { diff --git a/cinera/cinera_config.c b/cinera/cinera_config.c index 83a9ae0..5abed50 100644 --- a/cinera/cinera_config.c +++ b/cinera/cinera_config.c @@ -2958,7 +2958,8 @@ PushVariantUniquely(resolution_errors *E, variant_strings *VS, string Path, uint typedef struct { - string String; + string String0; + string String1; project **Projects; uint64_t ProjectCount; } config_string_association; @@ -2966,7 +2967,8 @@ typedef struct typedef struct { config_string_association *Associations; - config_identifier_id ID; + config_identifier_id ID0; + config_identifier_id ID1; uint64_t Count; } config_string_associations; @@ -2974,6 +2976,8 @@ typedef struct { variant_strings VariantStrings; config_string_associations HMMLDirs; + config_string_associations BaseDirAndSearchLocation; + config_string_associations BaseDirAndPlayerLocation; } config_verifiers; void @@ -3197,14 +3201,60 @@ AddProjectToAssociation(config_string_association *A, project *P) } void -PushHMMLDirAssociation(config_string_associations *HMMLDirs, string *S, project *P) +PushAssociation(config_string_associations *HMMLDirs, string *S0, string *S1, project *P) { HMMLDirs->Associations = Fit(HMMLDirs->Associations, sizeof(*HMMLDirs->Associations), HMMLDirs->Count, 8, TRUE); - HMMLDirs->Associations[HMMLDirs->Count].String = *S; + 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) { @@ -3212,7 +3262,7 @@ SetUniqueHMMLDir(config *C, resolution_errors *E, config_string_associations *HM bool Clashed = FALSE; for(int i = 0; i < HMMLDirs->Count; ++i) { - if(StringsMatch(HMMLDirs->Associations[i].String, Result)) + if(StringsMatch(HMMLDirs->Associations[i].String0, Result)) { AddProjectToAssociation(&HMMLDirs->Associations[i], P); PushError(E, S_ERROR, &HMMLDir->Position, IDENT_HMML_DIR); @@ -3223,7 +3273,7 @@ SetUniqueHMMLDir(config *C, resolution_errors *E, config_string_associations *HM if(!Clashed) { - PushHMMLDirAssociation(HMMLDirs, &Result, P); + PushAssociation(HMMLDirs, &Result, 0, P); } P->HMMLDir = Result; } @@ -3399,6 +3449,28 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc } } + 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); @@ -3511,16 +3583,35 @@ PrintAssociationClashes(config_string_associations *A) config_string_association *Association = A->Associations + i; if(Association->ProjectCount > 1) { - string ID = Wrap0(ConfigIdentifiers[A->ID].String); - ConfigError(0, 0, S_ERROR, "Multiple projects share the same ", &ID); - fprintf(stderr, " "); - PrintStringC(CS_MAGENTA_BOLD, Association->String); + 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, " "); - PrintStringC(CS_CYAN, P->Lineage); + 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"); } } @@ -3609,6 +3700,8 @@ FreeVariants(variant_strings *V) void FreeVerifiers(config_verifiers *Verifiers) { + FreeAssociations(&Verifiers->BaseDirAndSearchLocation); + FreeAssociations(&Verifiers->BaseDirAndPlayerLocation); FreeAssociations(&Verifiers->HMMLDirs); FreeVariants(&Verifiers->VariantStrings); } @@ -3623,7 +3716,11 @@ ResolveVariables(scope_tree *S) InitBook(&Result->Project, sizeof(project), 8, MBT_PROJECT); resolution_errors Errors = {}; config_verifiers Verifiers = {}; - Verifiers.HMMLDirs.ID = IDENT_HMML_DIR; + 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) { @@ -3752,6 +3849,13 @@ ResolveVariables(scope_tree *S) } } + 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) { @@ -3762,6 +3866,8 @@ ResolveVariables(scope_tree *S) if(Errors.ErrorCount > 0) { PrintAssociationClashes(&Verifiers.HMMLDirs); + PrintAssociationClashes(&Verifiers.BaseDirAndSearchLocation); + PrintAssociationClashes(&Verifiers.BaseDirAndPlayerLocation); PrintVariantInconsistencies(&Verifiers.VariantStrings); Free(Result); }