cinera.c: Abbreviate names better
This commit makes the auto-derivation of name abbreviations code handle
quoted nicknames and lower-cased surname prefixes (e.g. du Pré). It also
upstreams the abbreviation to config parse time, and introduces config
fields in the person scope to allow overwriting the auto-derived ones.
Bug fixes:
    •   Fix null pointer dereference in InsertProjectIntoDB(), due to a
        rogue WriteFromByteToPointer() call left in when removing the
        _TopLevel() database modification functions in v0.10.14
    •   Fix config file locking by closing all config files on detecting
        a config file change, even if we had no prior working config
    •   Increase IncludesSearch buffer size from 2 to 4KB
New config fields in the person scope:
    •   abbrev_initial
    •   abbrev_given_or_nickname
    •   abbrev_dotted_initial_and_surname
			
			
This commit is contained in:
		
							parent
							
								
									68d1469212
								
							
						
					
					
						commit
						0f15957cb5
					
				
							
								
								
									
										195
									
								
								cinera/cinera.c
								
								
								
								
							
							
						
						
									
										195
									
								
								cinera/cinera.c
								
								
								
								
							|  | @ -23,7 +23,7 @@ typedef struct | ||||||
| version CINERA_APP_VERSION = { | version CINERA_APP_VERSION = { | ||||||
|     .Major = 0, |     .Major = 0, | ||||||
|     .Minor = 10, |     .Minor = 10, | ||||||
|     .Patch = 15 |     .Patch = 16 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
 | #define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
 | ||||||
|  | @ -971,6 +971,17 @@ StringContains(string S, string Substring) | ||||||
|     return Result; |     return Result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | int | ||||||
|  | StringContainsXOfChar(string S, char C) | ||||||
|  | { | ||||||
|  |     int Result = 0; | ||||||
|  |     for(int i = 0; i < S.Length; ++i) | ||||||
|  |     { | ||||||
|  |         Result += S.Base[i] == C; | ||||||
|  |     } | ||||||
|  |     return Result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool | bool | ||||||
| StringsMatchCaseInsensitive(string A, string B) | StringsMatchCaseInsensitive(string A, string B) | ||||||
| { | { | ||||||
|  | @ -1413,6 +1424,9 @@ typedef struct | ||||||
| config_identifier ConfigIdentifiers[] = | config_identifier ConfigIdentifiers[] = | ||||||
| { | { | ||||||
|     { "" }, |     { "" }, | ||||||
|  |     { "abbrev_dotted_initial_and_surname", "The dotted-initial(s) and surname of a person's name, used to override the auto-derived one, e.g. J. R. R. Tolkien (from John Ronald Reuel Tolkien) or J. du Pré (from Jacqueline du Pré)" }, | ||||||
|  |     { "abbrev_given_or_nickname", "The given or quoted nickname of a person's name, used to override the auto-derived one, e.g. Charlotte (from Charlotte Brontë) or Ry (from Ryland Peter \"Ry\" Cooder)" }, | ||||||
|  |     { "abbrev_initial", "The initials of a person's name, used to override the auto-derived ones, e.g. KB (from Kate Bush)" }, | ||||||
|     { "allow", |     { "allow", | ||||||
| "An include-rule string of the forms: \"identifier\", \"type.member\" or \"type.member.member\", etc. For example:\n\nallow = \"project.default_medium\";\n\nAdding an \"allow\" / \"deny\" rule makes the inclusion prohibitive or permissive, respectively, \
 | "An include-rule string of the forms: \"identifier\", \"type.member\" or \"type.member.member\", etc. For example:\n\nallow = \"project.default_medium\";\n\nAdding an \"allow\" / \"deny\" rule makes the inclusion prohibitive or permissive, respectively, \
 | ||||||
| and they cannot be mixed (i.e. only \"allow\" rules, or only \"deny\" rules)." | and they cannot be mixed (i.e. only \"allow\" rules, or only \"deny\" rules)." | ||||||
|  | @ -1557,6 +1571,9 @@ fill the slots left vacant by positioned roles in the order in which they are co | ||||||
| typedef enum | typedef enum | ||||||
| { | { | ||||||
|     IDENT_NULL, |     IDENT_NULL, | ||||||
|  |     IDENT_ABBREV_DOTTED_INITIAL_AND_SURNAME, | ||||||
|  |     IDENT_ABBREV_GIVEN_OR_NICKNAME, | ||||||
|  |     IDENT_ABBREV_INITIAL, | ||||||
|     IDENT_ALLOW, |     IDENT_ALLOW, | ||||||
|     IDENT_ART, |     IDENT_ART, | ||||||
|     IDENT_ART_VARIANTS, |     IDENT_ART_VARIANTS, | ||||||
|  | @ -1944,6 +1961,22 @@ ConfigError(string *Filename, uint64_t LineNumber, severity Severity, char *Mess | ||||||
|     fprintf(stderr, "\n"); |     fprintf(stderr, "\n"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void | ||||||
|  | ConfigErrorField(string *Filename, uint64_t LineNumber, severity Severity, config_identifier_id FieldID, | ||||||
|  |         char *Message, string *Received) | ||||||
|  | { | ||||||
|  |     ErrorFilenameAndLineNumber(Filename, LineNumber, Severity, ED_CONFIG); | ||||||
|  |     fprintf(stderr, | ||||||
|  |             "Faulty %s%s%s %s", | ||||||
|  |             ColourStrings[CS_YELLOW_BOLD], ConfigIdentifiers[FieldID].String, ColourStrings[CS_END], | ||||||
|  |             Message); | ||||||
|  |     if(Received) | ||||||
|  |     { | ||||||
|  |         PrintStringC(CS_MAGENTA_BOLD, *Received); | ||||||
|  |     } | ||||||
|  |     fprintf(stderr, "\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void | void | ||||||
| ConfigErrorUnset(config_identifier_id FieldID) | ConfigErrorUnset(config_identifier_id FieldID) | ||||||
| { | { | ||||||
|  | @ -1952,6 +1985,19 @@ ConfigErrorUnset(config_identifier_id FieldID) | ||||||
|             "Unset %s\n", ConfigIdentifiers[FieldID].String); |             "Unset %s\n", ConfigIdentifiers[FieldID].String); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void | ||||||
|  | ConfigErrorUnsetFieldOf(string *Filename, uint64_t LineNumber, | ||||||
|  |         config_identifier_id UnsetFieldID, | ||||||
|  |         config_identifier_id ScopeKey, string ScopeID) | ||||||
|  | { | ||||||
|  |     ErrorFilenameAndLineNumber(Filename, LineNumber, S_ERROR, ED_CONFIG); | ||||||
|  |     fprintf(stderr, | ||||||
|  |             "Unset %s%s%s of %s%s%s: %s%.*s%s\n", | ||||||
|  |             ColourStrings[CS_YELLOW_BOLD], ConfigIdentifiers[UnsetFieldID].String, ColourStrings[CS_END], | ||||||
|  |             ColourStrings[CS_YELLOW_BOLD], ConfigIdentifiers[ScopeKey].String, ColourStrings[CS_END], | ||||||
|  |             ColourStrings[CS_GREEN_BOLD], (int)ScopeID.Length, ScopeID.Base, ColourStrings[CS_END]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void | void | ||||||
| ConfigErrorSizing(string *Filename, uint64_t LineNumber, config_identifier_id FieldID, string *Received, uint64_t MaxSize) | ConfigErrorSizing(string *Filename, uint64_t LineNumber, config_identifier_id FieldID, string *Received, uint64_t MaxSize) | ||||||
| { | { | ||||||
|  | @ -5091,14 +5137,13 @@ typedef struct | ||||||
| { | { | ||||||
|     hsl_colour       Colour; |     hsl_colour       Colour; | ||||||
|     person          *Person; |     person          *Person; | ||||||
|     string           Abbreviation; |  | ||||||
|     bool             Seen; |     bool             Seen; | ||||||
| } speaker; | } speaker; | ||||||
| 
 | 
 | ||||||
| typedef struct | typedef struct | ||||||
| { | { | ||||||
|     _memory_book(speaker) Speakers; |     _memory_book(speaker) Speakers; | ||||||
|     memory_book Abbreviations; |     abbreviation_scheme AbbrevScheme; | ||||||
| } speakers; | } speakers; | ||||||
| 
 | 
 | ||||||
| enum | enum | ||||||
|  | @ -6837,94 +6882,38 @@ ConstructResolvedAssetURL(buffer *Buffer, asset *Asset, page_type PageType) | ||||||
|     Free(ResolvablePath); |     Free(ResolvablePath); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| string | void | ||||||
| InitialString(memory_book *Abbreviations, string Src) | PickAbbreviationScheme(speakers *Speakers) | ||||||
| { | { | ||||||
|     ResetPen(Abbreviations); |     Speakers->AbbrevScheme = AS_NONE; | ||||||
|     string Result = {}; |     int SchemeCount = 3; | ||||||
|     string Char = Wrap0i_(Src.Base, 1); |     for(int SchemeIndex = 0; SchemeIndex < SchemeCount; ++SchemeIndex) | ||||||
| 
 |  | ||||||
|     Result = ExtendStringInBook(Abbreviations, Char); |  | ||||||
|     for(int i = 1; i < Src.Length; ++i) |  | ||||||
|     { |     { | ||||||
|         if(Src.Base[i] == ' ' && i < Src.Length) |         bool Clash = FALSE; | ||||||
|  |         for(int i = 0; i < Speakers->Speakers.ItemCount; ++i) | ||||||
|         { |         { | ||||||
|             ++i; |             speaker *A = GetPlaceInBook(&Speakers->Speakers, i); | ||||||
|             Char = Wrap0i_(Src.Base + i, 1); |             for(int j = i + 1; j < Speakers->Speakers.ItemCount; ++j) | ||||||
|             Result = ExtendStringInBook(Abbreviations, Char); |             { | ||||||
|  |                 speaker *B = GetPlaceInBook(&Speakers->Speakers, j); | ||||||
|  |                 if(StringsMatch(A->Person->Abbreviations[SchemeIndex], B->Person->Abbreviations[SchemeIndex])) | ||||||
|  |                 { | ||||||
|  |                     Clash = TRUE; | ||||||
|  |                     break; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|     return Result; |             if(Clash) { break; } | ||||||
| } |  | ||||||
| 
 |  | ||||||
| string |  | ||||||
| GetFirstSubstring(string Src) |  | ||||||
| { |  | ||||||
|     string Result = Src; |  | ||||||
|     for(Result.Length = 0; Result.Length < Src.Length && Result.Base[Result.Length] != ' '; ++Result.Length) { } |  | ||||||
|     return Result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| string |  | ||||||
| InitialAndGetFinalString(memory_book *Abbreviations, string Src) |  | ||||||
| { |  | ||||||
|     ResetPen(Abbreviations); |  | ||||||
|     string Result = {}; |  | ||||||
|     int FinalStringBase; |  | ||||||
|     for(FinalStringBase = Src.Length; FinalStringBase > 0; --FinalStringBase) |  | ||||||
|     { |  | ||||||
|         if(Src.Base[FinalStringBase - 1] == ' ') { break; } |  | ||||||
|         } |         } | ||||||
| 
 |         if(!Clash) | ||||||
|     if(FinalStringBase > 0 && Src.Base[FinalStringBase] == ' ' && FinalStringBase < Src.Length) |  | ||||||
|         { |         { | ||||||
|         ++FinalStringBase; |             Speakers->AbbrevScheme = SchemeIndex; | ||||||
|     } |             break; | ||||||
| 
 |  | ||||||
|     string FinalString = Wrap0i_(Src.Base + FinalStringBase, Src.Length - FinalStringBase); |  | ||||||
| 
 |  | ||||||
|     if(FinalStringBase > 0) |  | ||||||
|     { |  | ||||||
|         string Initial = Wrap0i_(Src.Base, 1); |  | ||||||
|         Result = ExtendStringInBook(Abbreviations, Initial); |  | ||||||
|         Result = ExtendStringInBook(Abbreviations, Wrap0(". ")); |  | ||||||
| 
 |  | ||||||
|         for(int i = 0; i < FinalStringBase; ++i) |  | ||||||
|         { |  | ||||||
|             if(Src.Base[i] == ' ' && i + 1 < FinalStringBase) |  | ||||||
|             { |  | ||||||
|                 ++i; |  | ||||||
|                 string Initial = Wrap0i_(Src.Base + i, 1); |  | ||||||
|                 Result = ExtendStringInBook(Abbreviations, Initial); |  | ||||||
|                 Result = ExtendStringInBook(Abbreviations, Wrap0(". ")); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Result = ExtendStringInBook(Abbreviations, FinalString); |  | ||||||
|     return Result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool |  | ||||||
| AbbreviationsClash(memory_book *Speakers) |  | ||||||
| { |  | ||||||
|     for(int i = 0; i < Speakers->ItemCount; ++i) |  | ||||||
|     { |  | ||||||
|         speaker *A = GetPlaceInBook(Speakers, i); |  | ||||||
|         for(int j = i + 1; j < Speakers->ItemCount; ++j) |  | ||||||
|         { |  | ||||||
|             speaker *B = GetPlaceInBook(Speakers, j); |  | ||||||
|             if(StringsMatch(A->Abbreviation, B->Abbreviation)) |  | ||||||
|             { |  | ||||||
|                 return TRUE; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return FALSE; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void | void | ||||||
| SortAndAbbreviateSpeakers(speakers *Speakers) | SortSpeakersAndPickAbbreviationScheme(speakers *Speakers) | ||||||
| { | { | ||||||
|     for(int i = 0; i < Speakers->Speakers.ItemCount; ++i) |     for(int i = 0; i < Speakers->Speakers.ItemCount; ++i) | ||||||
|     { |     { | ||||||
|  | @ -6945,26 +6934,10 @@ SortAndAbbreviateSpeakers(speakers *Speakers) | ||||||
|     for(int i = 0; i < Speakers->Speakers.ItemCount; ++i) |     for(int i = 0; i < Speakers->Speakers.ItemCount; ++i) | ||||||
|     { |     { | ||||||
|         speaker *This = GetPlaceInBook(&Speakers->Speakers, i); |         speaker *This = GetPlaceInBook(&Speakers->Speakers, i); | ||||||
|         string Name = This->Person->Name.Length > 0 ? This->Person->Name : This->Person->ID; |  | ||||||
|         StringToColourHash(&This->Colour, This->Person->ID); |         StringToColourHash(&This->Colour, This->Person->ID); | ||||||
|         This->Abbreviation = InitialString(&Speakers->Abbreviations, Name); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     int MaxAttemptCount = 3; |     PickAbbreviationScheme(Speakers); | ||||||
|     for(int Attempt = 0; Attempt < MaxAttemptCount && AbbreviationsClash(&Speakers->Speakers); ++Attempt) |  | ||||||
|     { |  | ||||||
|         for(int i = 0; i < Speakers->Speakers.ItemCount; ++i) |  | ||||||
|         { |  | ||||||
|             speaker *This = GetPlaceInBook(&Speakers->Speakers, i); |  | ||||||
|             string Name = This->Person->Name.Length > 0 ? This->Person->Name : This->Person->ID; |  | ||||||
|             switch(Attempt) |  | ||||||
|             { |  | ||||||
|                 case 0: This->Abbreviation = GetFirstSubstring(Name); break; |  | ||||||
|                 case 1: This->Abbreviation = InitialAndGetFinalString(&Speakers->Abbreviations, Name); break; |  | ||||||
|                 case 2: This->Abbreviation = Name; break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| person * | person * | ||||||
|  | @ -7359,7 +7332,6 @@ void | ||||||
| FreeSpeakers(speakers *Speakers) | FreeSpeakers(speakers *Speakers) | ||||||
| { | { | ||||||
|     FreeBook(&Speakers->Speakers); |     FreeBook(&Speakers->Speakers); | ||||||
|     FreeBook(&Speakers->Abbreviations); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void | void | ||||||
|  | @ -7644,11 +7616,11 @@ BuildCredits(string HMMLFilepath, buffer *CreditsMenu, HMML_VideoMetaData *Metad | ||||||
|     } |     } | ||||||
|     FreeBook(&Credits); |     FreeBook(&Credits); | ||||||
| 
 | 
 | ||||||
|     // NOTE(matt):  As we only cite the speaker when there are a multiple of them, we only need to SortAndAbbreviateSpeakers()
 |     // NOTE(matt):  As we only cite the speaker when there are a multiple of them, we only
 | ||||||
|     //              in the same situation
 |     //              need to SortSpeakersAndPickAbbreviationScheme() in the same situation
 | ||||||
|     if(Speakers->Speakers.ItemCount > 1) |     if(Speakers->Speakers.ItemCount > 1) | ||||||
|     { |     { | ||||||
|         SortAndAbbreviateSpeakers(Speakers); |         SortSpeakersAndPickAbbreviationScheme(Speakers); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(CreditsMenu->Ptr > CreditsMenu->Location) |     if(CreditsMenu->Ptr > CreditsMenu->Location) | ||||||
|  | @ -10093,7 +10065,6 @@ InitSpeakers() | ||||||
| { | { | ||||||
|     speakers Result = {}; |     speakers Result = {}; | ||||||
|     Result.Speakers = InitBook(sizeof(speaker), 4); |     Result.Speakers = InitBook(sizeof(speaker), 4); | ||||||
|     Result.Abbreviations = InitBookOfStrings(64); |  | ||||||
|     return Result; |     return Result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -10385,14 +10356,22 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m | ||||||
|             // NOTE(matt): I reckon it's fair to only cite the speaker when there are a multiple of them
 |             // NOTE(matt): I reckon it's fair to only cite the speaker when there are a multiple of them
 | ||||||
|             if(Speakers->Speakers.ItemCount > 1 && Speaker && !IsCategorisedAuthored(Timestamp)) |             if(Speakers->Speakers.ItemCount > 1 && Speaker && !IsCategorisedAuthored(Timestamp)) | ||||||
|             { |             { | ||||||
|                 string DisplayName = !Speaker->Seen ? Speaker->Person->Name : Speaker->Abbreviation; |                 string DisplayName = !Speaker->Seen ? Speaker->Person->Name : Speaker->Person->Abbreviations[Speakers->AbbrevScheme]; | ||||||
| 
 | 
 | ||||||
|                 CopyStringToBuffer(&IndexBuffers->Text, |                 CopyStringToBuffer(&IndexBuffers->Text, | ||||||
|                         "<span class=\"author\" data-hue=\"%d\" data-saturation=\"%d%%\">%.*s</span>: ", |                         "<span class=\"author\" data-hue=\"%d\" data-saturation=\"%d%%\"", | ||||||
|                         Speaker->Colour.Hue, |                         Speaker->Colour.Hue, | ||||||
|                         Speaker->Colour.Saturation, |                         Speaker->Colour.Saturation); | ||||||
| 
 | 
 | ||||||
|                         (int)DisplayName.Length, DisplayName.Base); |                 if(Speaker->Seen) | ||||||
|  |                 { | ||||||
|  |                     CopyStringToBuffer(&IndexBuffers->Text, " title=\""); | ||||||
|  |                     CopyStringToBufferHTMLSafe(&IndexBuffers->Text, Speaker->Person->Name); | ||||||
|  |                     CopyStringToBuffer(&IndexBuffers->Text, "\""); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 CopyStringToBuffer(&IndexBuffers->Text, | ||||||
|  |                         ">%.*s</span>: ", (int)DisplayName.Length, DisplayName.Base); | ||||||
| 
 | 
 | ||||||
|                 Speaker->Seen = TRUE; |                 Speaker->Seen = TRUE; | ||||||
|             } |             } | ||||||
|  | @ -16084,7 +16063,6 @@ InsertProjectIntoDB(project_generations *G, db_block_projects **Block, db_header | ||||||
|     uint64_t Byte = 0; |     uint64_t Byte = 0; | ||||||
| 
 | 
 | ||||||
|     OpenFileForWriting(&DB.Metadata.File); |     OpenFileForWriting(&DB.Metadata.File); | ||||||
|     WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Block); |  | ||||||
| 
 | 
 | ||||||
|     uint64_t PPos; |     uint64_t PPos; | ||||||
|     if(GotBlock) |     if(GotBlock) | ||||||
|  | @ -17257,12 +17235,9 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke | ||||||
|                         } |                         } | ||||||
|                     } break; |                     } break; | ||||||
|                 case WT_CONFIG: |                 case WT_CONFIG: | ||||||
|                     { |  | ||||||
|                         if(Config) |  | ||||||
|                     { |                     { | ||||||
|                         DiscardAllAndFreeConfig(); |                         DiscardAllAndFreeConfig(); | ||||||
|                         PushWatchHandle(ConfigPath, EXT_NULL, WT_CONFIG, 0, 0); |                         PushWatchHandle(ConfigPath, EXT_NULL, WT_CONFIG, 0, 0); | ||||||
|                         } |  | ||||||
| 
 | 
 | ||||||
|                         ParseAndEitherPrintConfigOrInitAll(ConfigPath, TokensList, N, CollationBuffers, BespokeTemplate); |                         ParseAndEitherPrintConfigOrInitAll(ConfigPath, TokensList, N, CollationBuffers, BespokeTemplate); | ||||||
|                     } break; |                     } break; | ||||||
|  | @ -17400,7 +17375,7 @@ main(int ArgC, char **Args) | ||||||
|         if(ClaimBuffer(&CollationBuffers.IncludesPlayer, BID_COLLATION_BUFFERS_INCLUDES_PLAYER, Kilobytes(2)) == RC_ARENA_FULL) { Exit(); }; |         if(ClaimBuffer(&CollationBuffers.IncludesPlayer, BID_COLLATION_BUFFERS_INCLUDES_PLAYER, Kilobytes(2)) == RC_ARENA_FULL) { Exit(); }; | ||||||
|         if(ClaimBuffer(&CollationBuffers.Player, BID_COLLATION_BUFFERS_PLAYER, Kilobytes(552)) == RC_ARENA_FULL) { Exit(); }; |         if(ClaimBuffer(&CollationBuffers.Player, BID_COLLATION_BUFFERS_PLAYER, Kilobytes(552)) == RC_ARENA_FULL) { Exit(); }; | ||||||
| 
 | 
 | ||||||
|         if(ClaimBuffer(&CollationBuffers.IncludesSearch, BID_COLLATION_BUFFERS_INCLUDES_SEARCH, Kilobytes(2)) == RC_ARENA_FULL) { Exit(); }; |         if(ClaimBuffer(&CollationBuffers.IncludesSearch, BID_COLLATION_BUFFERS_INCLUDES_SEARCH, Kilobytes(4)) == RC_ARENA_FULL) { Exit(); }; | ||||||
|         if(ClaimBuffer(&CollationBuffers.SearchEntry, BID_COLLATION_BUFFERS_SEARCH_ENTRY, Kilobytes(32)) == RC_ARENA_FULL) { Exit(); }; |         if(ClaimBuffer(&CollationBuffers.SearchEntry, BID_COLLATION_BUFFERS_SEARCH_ENTRY, Kilobytes(32)) == RC_ARENA_FULL) { Exit(); }; | ||||||
| 
 | 
 | ||||||
|         CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer()
 |         CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer()
 | ||||||
|  |  | ||||||
|  | @ -480,11 +480,21 @@ typedef struct | ||||||
|     bool Hidden; |     bool Hidden; | ||||||
| } medium; | } medium; | ||||||
| 
 | 
 | ||||||
|  | typedef enum | ||||||
|  | { | ||||||
|  |     AS_INITIAL, | ||||||
|  |     AS_GIVEN_OR_NICKNAME, | ||||||
|  |     AS_DOTTED_INITIAL_AND_SURNAME, | ||||||
|  |     AS_NONE, | ||||||
|  |     AS_COUNT | ||||||
|  | } abbreviation_scheme; | ||||||
|  | 
 | ||||||
| typedef struct | typedef struct | ||||||
| { | { | ||||||
|     string ID; |     string ID; | ||||||
|     string Name; |     string Name; | ||||||
|     // TODO(matt):  string SortName;
 |     // TODO(matt):  string SortName;
 | ||||||
|  |     string Abbreviations[AS_COUNT]; | ||||||
|     string QuoteUsername; |     string QuoteUsername; | ||||||
|     string Homepage; |     string Homepage; | ||||||
|     _memory_book(support) Support; |     _memory_book(support) Support; | ||||||
|  | @ -979,6 +989,9 @@ InitTypeSpecs(void) | ||||||
| 
 | 
 | ||||||
|     config_type_spec *Person = PushTypeSpec(&Result, IDENT_PERSON, FALSE); |     config_type_spec *Person = PushTypeSpec(&Result, IDENT_PERSON, FALSE); | ||||||
|     PushTypeSpecField(Person, FT_STRING, IDENT_NAME, TRUE); |     PushTypeSpecField(Person, FT_STRING, IDENT_NAME, TRUE); | ||||||
|  |     PushTypeSpecField(Person, FT_STRING, IDENT_ABBREV_INITIAL, TRUE); | ||||||
|  |     PushTypeSpecField(Person, FT_STRING, IDENT_ABBREV_GIVEN_OR_NICKNAME, TRUE); | ||||||
|  |     PushTypeSpecField(Person, FT_STRING, IDENT_ABBREV_DOTTED_INITIAL_AND_SURNAME, TRUE); | ||||||
|     PushTypeSpecField(Person, FT_STRING, IDENT_HOMEPAGE, TRUE); |     PushTypeSpecField(Person, FT_STRING, IDENT_HOMEPAGE, TRUE); | ||||||
|     PushTypeSpecField(Person, FT_STRING, IDENT_QUOTE_USERNAME, TRUE); |     PushTypeSpecField(Person, FT_STRING, IDENT_QUOTE_USERNAME, TRUE); | ||||||
|     PushTypeSpecField(Person, FT_SCOPE, IDENT_SUPPORT, FALSE); |     PushTypeSpecField(Person, FT_SCOPE, IDENT_SUPPORT, FALSE); | ||||||
|  | @ -3231,6 +3244,230 @@ PushSupport(config *C, resolution_errors *E, config_verifiers *V, person *P, sco | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void | ||||||
|  | InitialSubstring(memory_book *Abbreviations, | ||||||
|  |         string *Dest, string Src, int Extent, string PostInitialString) | ||||||
|  | { | ||||||
|  |     int SrcIndex = 0; | ||||||
|  |     bool InQuote = FALSE; | ||||||
|  |     string Initial; | ||||||
|  |     int StepSize = 1; | ||||||
|  |     if(Src.Base[SrcIndex] == '\"') | ||||||
|  |     { | ||||||
|  |         InQuote = !InQuote; | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |         Initial = GetUTF8Character(Src.Base + SrcIndex, Extent - SrcIndex); | ||||||
|  |         *Dest = ExtendStringInBook(Abbreviations, Initial); | ||||||
|  |         *Dest = ExtendStringInBook(Abbreviations, PostInitialString); | ||||||
|  |         StepSize = Initial.Length; | ||||||
|  |     } | ||||||
|  |     SrcIndex += StepSize; | ||||||
|  | 
 | ||||||
|  |     for(; SrcIndex < Extent; SrcIndex += StepSize) | ||||||
|  |     { | ||||||
|  |         StepSize = 1; | ||||||
|  |         switch(Src.Base[SrcIndex]) | ||||||
|  |         { | ||||||
|  |             case ' ': | ||||||
|  |                 { | ||||||
|  |                     if(!InQuote | ||||||
|  |                             && SrcIndex + 1 < Extent && Src.Base[SrcIndex + 1] != '\"') | ||||||
|  |                     { | ||||||
|  |                         Initial = GetUTF8Character(Src.Base + SrcIndex + 1, Extent - SrcIndex - 1); | ||||||
|  |                         *Dest = ExtendStringInBook(Abbreviations, Initial); | ||||||
|  |                         *Dest = ExtendStringInBook(Abbreviations, PostInitialString); | ||||||
|  |                         StepSize = Initial.Length; | ||||||
|  |                     } | ||||||
|  |                 } break; | ||||||
|  |             case '\"': | ||||||
|  |                 { | ||||||
|  |                     InQuote = !InQuote; | ||||||
|  |                 } break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string | ||||||
|  | InitialString(memory_book *Abbreviations, string Src) | ||||||
|  | { | ||||||
|  |     ResetPen(Abbreviations); | ||||||
|  |     string Result = {}; | ||||||
|  |     InitialSubstring(Abbreviations, &Result, Src, Src.Length, Wrap0("")); | ||||||
|  |     return Result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string | ||||||
|  | GetQuotedOrFirstSubstring(string Src) | ||||||
|  | { | ||||||
|  |     string Result; | ||||||
|  | 
 | ||||||
|  |     string QuotedSubstring = {}; | ||||||
|  |     string FirstSubstring = Src; | ||||||
|  |     bool GotFirstSubstring = FALSE; | ||||||
|  |     bool InQuote = FALSE; | ||||||
|  | 
 | ||||||
|  |     for(int SrcIndex = 0; SrcIndex < Src.Length && !QuotedSubstring.Length; ++SrcIndex) | ||||||
|  |     { | ||||||
|  |         switch(Src.Base[SrcIndex]) | ||||||
|  |         { | ||||||
|  |             case ' ': | ||||||
|  |                 { | ||||||
|  |                     if(!InQuote && !GotFirstSubstring) | ||||||
|  |                     { | ||||||
|  |                         FirstSubstring.Base = Src.Base; | ||||||
|  |                         FirstSubstring.Length = SrcIndex; | ||||||
|  |                         GotFirstSubstring = TRUE; | ||||||
|  |                     } | ||||||
|  |                 } break; | ||||||
|  |             case '\"': | ||||||
|  |                 { | ||||||
|  |                     if(!InQuote) | ||||||
|  |                     { | ||||||
|  |                         if(SrcIndex + 1 < Src.Length) | ||||||
|  |                         { | ||||||
|  |                             QuotedSubstring.Base = Src.Base + SrcIndex + 1; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         QuotedSubstring.Length = SrcIndex - (QuotedSubstring.Base - Src.Base); | ||||||
|  |                     } | ||||||
|  |                     InQuote = !InQuote; | ||||||
|  |                 } break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Result = QuotedSubstring.Length ? QuotedSubstring : FirstSubstring; | ||||||
|  |     return Result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string | ||||||
|  | StripQuotedStringStart(string S) | ||||||
|  | { | ||||||
|  |     string Result = S; | ||||||
|  |     if(Result.Length > 0 && Result.Base[0] == '\"') | ||||||
|  |     { | ||||||
|  |         ++Result.Base; | ||||||
|  |         --Result.Length; | ||||||
|  |         while(Result.Length > 0 && Result.Base[0] != '\"') | ||||||
|  |         { | ||||||
|  |             ++Result.Base; | ||||||
|  |             --Result.Length; | ||||||
|  |         } | ||||||
|  |         Assert(Result.Length > 0); | ||||||
|  |         ++Result.Base; | ||||||
|  |         --Result.Length; | ||||||
|  |     } | ||||||
|  |     return Result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string | ||||||
|  | StripQuotedStringEnd(string S) | ||||||
|  | { | ||||||
|  |     string Result = S; | ||||||
|  |     if(Result.Length > 0 && Result.Base[Result.Length - 1] == '\"') | ||||||
|  |     { | ||||||
|  |         --Result.Length; | ||||||
|  |         while(Result.Length > 0 && Result.Base[Result.Length - 1] != '\"') | ||||||
|  |         { | ||||||
|  |             --Result.Length; | ||||||
|  |         } | ||||||
|  |         Assert(Result.Length > 0); | ||||||
|  |         --Result.Length; | ||||||
|  |     } | ||||||
|  |     return Result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool | ||||||
|  | IsLower(char C) | ||||||
|  | { | ||||||
|  |     return C >= 'a' && C <= 'z'; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string | ||||||
|  | DottedInitialAndGetSurname(memory_book *Abbreviations, string Src) | ||||||
|  | { | ||||||
|  |     ResetPen(Abbreviations); | ||||||
|  |     string Result = {}; | ||||||
|  |     bool GotSurnameBase = FALSE; | ||||||
|  |     string WorkingSrc = Src; | ||||||
|  | 
 | ||||||
|  |     WorkingSrc = StripQuotedStringStart(WorkingSrc); | ||||||
|  |     WorkingSrc = StripQuotedStringEnd(WorkingSrc); | ||||||
|  |     WorkingSrc = TrimWhitespace(WorkingSrc); | ||||||
|  | 
 | ||||||
|  |     int PossibleSurnameBase = WorkingSrc.Length - 1; | ||||||
|  |     int SurnameBase = PossibleSurnameBase; | ||||||
|  | 
 | ||||||
|  |     for(; PossibleSurnameBase > 0; --PossibleSurnameBase) | ||||||
|  |     { | ||||||
|  |         char Prev = WorkingSrc.Base[PossibleSurnameBase - 1]; | ||||||
|  |         if(Prev == ' ') | ||||||
|  |         { | ||||||
|  |             if(!GotSurnameBase) | ||||||
|  |             { | ||||||
|  |                 SurnameBase = PossibleSurnameBase; | ||||||
|  |             } | ||||||
|  |             else if(IsLower(WorkingSrc.Base[PossibleSurnameBase])) | ||||||
|  |             { | ||||||
|  |                 SurnameBase = PossibleSurnameBase; | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             GotSurnameBase = TRUE; | ||||||
|  |         } | ||||||
|  |         else if(Prev == '\"') | ||||||
|  |         { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(!GotSurnameBase) | ||||||
|  |     { | ||||||
|  |         SurnameBase = PossibleSurnameBase; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(SurnameBase > 0 && WorkingSrc.Base[SurnameBase] == ' ' && SurnameBase < WorkingSrc.Length) | ||||||
|  |     { | ||||||
|  |         ++SurnameBase; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string Surname = Wrap0i_(WorkingSrc.Base + SurnameBase, WorkingSrc.Length - SurnameBase); | ||||||
|  | 
 | ||||||
|  |     if(SurnameBase > 0) | ||||||
|  |     { | ||||||
|  |         InitialSubstring(Abbreviations, &Result, WorkingSrc, SurnameBase, Wrap0(". ")); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Result = ExtendStringInBook(Abbreviations, Surname); | ||||||
|  |     return Result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void | ||||||
|  | AbbreviateName(config *C, person *P) | ||||||
|  | { | ||||||
|  |     if(P->Abbreviations[AS_INITIAL].Length == 0) | ||||||
|  |     { | ||||||
|  |         P->Abbreviations[AS_INITIAL] = InitialString(&C->ResolvedVariables, P->Name); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(P->Abbreviations[AS_GIVEN_OR_NICKNAME].Length == 0) | ||||||
|  |     { | ||||||
|  |         P->Abbreviations[AS_GIVEN_OR_NICKNAME] = GetQuotedOrFirstSubstring(P->Name); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(P->Abbreviations[AS_DOTTED_INITIAL_AND_SURNAME].Length == 0) | ||||||
|  |     { | ||||||
|  |         P->Abbreviations[AS_DOTTED_INITIAL_AND_SURNAME] = DottedInitialAndGetSurname(&C->ResolvedVariables, P->Name); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     P->Abbreviations[AS_NONE] = P->Name; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void | void | ||||||
| PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *PersonTree) | PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope_tree *PersonTree) | ||||||
| { | { | ||||||
|  | @ -3238,12 +3475,30 @@ PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope | ||||||
|     //PrintScopeTree(PersonTree);
 |     //PrintScopeTree(PersonTree);
 | ||||||
|     person *This = MakeSpaceInBook(&C->Person); |     person *This = MakeSpaceInBook(&C->Person); | ||||||
|     This->ID = ResolveString(C, E, PersonTree, &PersonTree->ID, FALSE); |     This->ID = ResolveString(C, E, PersonTree, &PersonTree->ID, FALSE); | ||||||
|  |     bool NamingError = FALSE; | ||||||
|     for(int i = 0; i < PersonTree->Pairs.ItemCount; ++i) |     for(int i = 0; i < PersonTree->Pairs.ItemCount; ++i) | ||||||
|     { |     { | ||||||
|         config_pair *Pair = GetPlaceInBook(&PersonTree->Pairs, i); |         config_pair *Pair = GetPlaceInBook(&PersonTree->Pairs, i); | ||||||
|         if(IDENT_NAME == Pair->Key) |         if(IDENT_NAME == Pair->Key) | ||||||
|         { |         { | ||||||
|             This->Name = ResolveString(C, E, PersonTree, Pair, FALSE); |             This->Name = ResolveString(C, E, PersonTree, Pair, FALSE); | ||||||
|  |             int QuotemarkCount = StringContainsXOfChar(This->Name, '\"'); | ||||||
|  |             if(QuotemarkCount == 1) | ||||||
|  |             { | ||||||
|  |                 string Filepath = Wrap0(Pair->Position.Filename); | ||||||
|  |                 ConfigErrorField(&Filepath, Pair->Position.LineNumber, S_ERROR, IDENT_NAME, | ||||||
|  |                         "contains unpaired quotation mark: ", &This->Name); | ||||||
|  |                 PushError(E, S_ERROR, 0, Pair->Key); | ||||||
|  |                 NamingError = TRUE; | ||||||
|  |             } | ||||||
|  |             else if(QuotemarkCount > 2) | ||||||
|  |             { | ||||||
|  |                 string Filepath = Wrap0(Pair->Position.Filename); | ||||||
|  |                 ConfigErrorField(&Filepath, Pair->Position.LineNumber, S_ERROR, IDENT_NAME, | ||||||
|  |                         "contains more than one pair of quotation marks: ", &This->Name); | ||||||
|  |                 PushError(E, S_ERROR, 0, Pair->Key); | ||||||
|  |                 NamingError = TRUE; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         else if(IDENT_HOMEPAGE == Pair->Key) |         else if(IDENT_HOMEPAGE == Pair->Key) | ||||||
|         { |         { | ||||||
|  | @ -3255,6 +3510,20 @@ PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if(This->Name.Length == 0) | ||||||
|  |     { | ||||||
|  |         string Filepath = Wrap0(PersonTree->ID.Position.Filename); | ||||||
|  |         ConfigErrorUnsetFieldOf(&Filepath, PersonTree->ID.Position.LineNumber, | ||||||
|  |                 IDENT_NAME, IDENT_PERSON, This->ID); | ||||||
|  |         PushError(E, S_ERROR, 0, IDENT_NAME); | ||||||
|  |         NamingError = TRUE; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(!NamingError) | ||||||
|  |     { | ||||||
|  |         AbbreviateName(C, This); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if(This->QuoteUsername.Length == 0) |     if(This->QuoteUsername.Length == 0) | ||||||
|     { |     { | ||||||
|         This->QuoteUsername = This->ID; |         This->QuoteUsername = This->ID; | ||||||
|  | @ -4113,6 +4382,9 @@ GetRowsRequiredForPersonInfo(typography *T, person *P) | ||||||
|     uint8_t RowsRequired = 0; |     uint8_t RowsRequired = 0; | ||||||
| 
 | 
 | ||||||
|     if(P->Name.Length > 0) { ++RowsRequired; } |     if(P->Name.Length > 0) { ++RowsRequired; } | ||||||
|  |     if(P->Abbreviations[AS_INITIAL].Length > 0) { ++RowsRequired; } | ||||||
|  |     if(P->Abbreviations[AS_GIVEN_OR_NICKNAME].Length > 0) { ++RowsRequired; } | ||||||
|  |     if(P->Abbreviations[AS_DOTTED_INITIAL_AND_SURNAME].Length > 0) { ++RowsRequired; } | ||||||
|     if(P->Homepage.Length > 0) { ++RowsRequired; } |     if(P->Homepage.Length > 0) { ++RowsRequired; } | ||||||
|     for(int i = 0; i < P->Support.ItemCount; ++i) |     for(int i = 0; i < P->Support.ItemCount; ++i) | ||||||
|     { |     { | ||||||
|  | @ -4150,6 +4422,27 @@ PrintPerson(person *P, typography *Typography) | ||||||
|         --RowsRequired; |         --RowsRequired; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if(P->Abbreviations[AS_INITIAL].Length > 0) | ||||||
|  |     { | ||||||
|  |         fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical); | ||||||
|  |         config_pair AbbrevInitial = { .Key = IDENT_ABBREV_INITIAL, .String = P->Abbreviations[AS_INITIAL], .Type = PT_STRING }; PrintPair(&AbbrevInitial, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE); | ||||||
|  |         --RowsRequired; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(P->Abbreviations[AS_GIVEN_OR_NICKNAME].Length > 0) | ||||||
|  |     { | ||||||
|  |         fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical); | ||||||
|  |         config_pair AbbrevGivenOrNickname = { .Key = IDENT_ABBREV_GIVEN_OR_NICKNAME, .String = P->Abbreviations[AS_GIVEN_OR_NICKNAME], .Type = PT_STRING }; PrintPair(&AbbrevGivenOrNickname, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE); | ||||||
|  |         --RowsRequired; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(P->Abbreviations[AS_DOTTED_INITIAL_AND_SURNAME].Length > 0) | ||||||
|  |     { | ||||||
|  |         fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical); | ||||||
|  |         config_pair AbbrevDottedInitialAndSurname = { .Key = IDENT_ABBREV_DOTTED_INITIAL_AND_SURNAME, .String = P->Abbreviations[AS_DOTTED_INITIAL_AND_SURNAME], .Type = PT_STRING }; PrintPair(&AbbrevDottedInitialAndSurname, Typography->Delimiter, ShouldFillSyntax, IndentationLevel, FALSE, TRUE); | ||||||
|  |         --RowsRequired; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if(P->Homepage.Length > 0) |     if(P->Homepage.Length > 0) | ||||||
|     { |     { | ||||||
|         fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical); |         fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue