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
					
				
							
								
								
									
										199
									
								
								cinera/cinera.c
								
								
								
								
							
							
						
						
									
										199
									
								
								cinera/cinera.c
								
								
								
								
							| 
						 | 
				
			
			@ -23,7 +23,7 @@ typedef struct
 | 
			
		|||
version CINERA_APP_VERSION = {
 | 
			
		||||
    .Major = 0,
 | 
			
		||||
    .Minor = 10,
 | 
			
		||||
    .Patch = 15
 | 
			
		||||
    .Patch = 16
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
 | 
			
		||||
| 
						 | 
				
			
			@ -971,6 +971,17 @@ StringContains(string S, string Substring)
 | 
			
		|||
    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
 | 
			
		||||
StringsMatchCaseInsensitive(string A, string B)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -1413,6 +1424,9 @@ typedef struct
 | 
			
		|||
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",
 | 
			
		||||
"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)."
 | 
			
		||||
| 
						 | 
				
			
			@ -1557,6 +1571,9 @@ fill the slots left vacant by positioned roles in the order in which they are co
 | 
			
		|||
typedef enum
 | 
			
		||||
{
 | 
			
		||||
    IDENT_NULL,
 | 
			
		||||
    IDENT_ABBREV_DOTTED_INITIAL_AND_SURNAME,
 | 
			
		||||
    IDENT_ABBREV_GIVEN_OR_NICKNAME,
 | 
			
		||||
    IDENT_ABBREV_INITIAL,
 | 
			
		||||
    IDENT_ALLOW,
 | 
			
		||||
    IDENT_ART,
 | 
			
		||||
    IDENT_ART_VARIANTS,
 | 
			
		||||
| 
						 | 
				
			
			@ -1944,6 +1961,22 @@ ConfigError(string *Filename, uint64_t LineNumber, severity Severity, char *Mess
 | 
			
		|||
    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
 | 
			
		||||
ConfigErrorUnset(config_identifier_id FieldID)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -1952,6 +1985,19 @@ ConfigErrorUnset(config_identifier_id FieldID)
 | 
			
		|||
            "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
 | 
			
		||||
ConfigErrorSizing(string *Filename, uint64_t LineNumber, config_identifier_id FieldID, string *Received, uint64_t MaxSize)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -5091,14 +5137,13 @@ typedef struct
 | 
			
		|||
{
 | 
			
		||||
    hsl_colour       Colour;
 | 
			
		||||
    person          *Person;
 | 
			
		||||
    string           Abbreviation;
 | 
			
		||||
    bool             Seen;
 | 
			
		||||
} speaker;
 | 
			
		||||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
    _memory_book(speaker) Speakers;
 | 
			
		||||
    memory_book Abbreviations;
 | 
			
		||||
    abbreviation_scheme AbbrevScheme;
 | 
			
		||||
} speakers;
 | 
			
		||||
 | 
			
		||||
enum
 | 
			
		||||
| 
						 | 
				
			
			@ -6837,94 +6882,38 @@ ConstructResolvedAssetURL(buffer *Buffer, asset *Asset, page_type PageType)
 | 
			
		|||
    Free(ResolvablePath);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
string
 | 
			
		||||
InitialString(memory_book *Abbreviations, string Src)
 | 
			
		||||
void
 | 
			
		||||
PickAbbreviationScheme(speakers *Speakers)
 | 
			
		||||
{
 | 
			
		||||
    ResetPen(Abbreviations);
 | 
			
		||||
    string Result = {};
 | 
			
		||||
    string Char = Wrap0i_(Src.Base, 1);
 | 
			
		||||
 | 
			
		||||
    Result = ExtendStringInBook(Abbreviations, Char);
 | 
			
		||||
    for(int i = 1; i < Src.Length; ++i)
 | 
			
		||||
    Speakers->AbbrevScheme = AS_NONE;
 | 
			
		||||
    int SchemeCount = 3;
 | 
			
		||||
    for(int SchemeIndex = 0; SchemeIndex < SchemeCount; ++SchemeIndex)
 | 
			
		||||
    {
 | 
			
		||||
        if(Src.Base[i] == ' ' && i < Src.Length)
 | 
			
		||||
        bool Clash = FALSE;
 | 
			
		||||
        for(int i = 0; i < Speakers->Speakers.ItemCount; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            ++i;
 | 
			
		||||
            Char = Wrap0i_(Src.Base + i, 1);
 | 
			
		||||
            Result = ExtendStringInBook(Abbreviations, Char);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return Result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(FinalStringBase > 0 && Src.Base[FinalStringBase] == ' ' && FinalStringBase < Src.Length)
 | 
			
		||||
    {
 | 
			
		||||
        ++FinalStringBase;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
            speaker *A = GetPlaceInBook(&Speakers->Speakers, i);
 | 
			
		||||
            for(int j = i + 1; j < Speakers->Speakers.ItemCount; ++j)
 | 
			
		||||
            {
 | 
			
		||||
                ++i;
 | 
			
		||||
                string Initial = Wrap0i_(Src.Base + i, 1);
 | 
			
		||||
                Result = ExtendStringInBook(Abbreviations, Initial);
 | 
			
		||||
                Result = ExtendStringInBook(Abbreviations, Wrap0(". "));
 | 
			
		||||
                speaker *B = GetPlaceInBook(&Speakers->Speakers, j);
 | 
			
		||||
                if(StringsMatch(A->Person->Abbreviations[SchemeIndex], B->Person->Abbreviations[SchemeIndex]))
 | 
			
		||||
                {
 | 
			
		||||
                    Clash = TRUE;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if(Clash) { break; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
        if(!Clash)
 | 
			
		||||
        {
 | 
			
		||||
            speaker *B = GetPlaceInBook(Speakers, j);
 | 
			
		||||
            if(StringsMatch(A->Abbreviation, B->Abbreviation))
 | 
			
		||||
            {
 | 
			
		||||
                return TRUE;
 | 
			
		||||
            }
 | 
			
		||||
            Speakers->AbbrevScheme = SchemeIndex;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return FALSE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
SortAndAbbreviateSpeakers(speakers *Speakers)
 | 
			
		||||
SortSpeakersAndPickAbbreviationScheme(speakers *Speakers)
 | 
			
		||||
{
 | 
			
		||||
    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)
 | 
			
		||||
    {
 | 
			
		||||
        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);
 | 
			
		||||
        This->Abbreviation = InitialString(&Speakers->Abbreviations, Name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int MaxAttemptCount = 3;
 | 
			
		||||
    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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    PickAbbreviationScheme(Speakers);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
person *
 | 
			
		||||
| 
						 | 
				
			
			@ -7359,7 +7332,6 @@ void
 | 
			
		|||
FreeSpeakers(speakers *Speakers)
 | 
			
		||||
{
 | 
			
		||||
    FreeBook(&Speakers->Speakers);
 | 
			
		||||
    FreeBook(&Speakers->Abbreviations);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
| 
						 | 
				
			
			@ -7644,11 +7616,11 @@ BuildCredits(string HMMLFilepath, buffer *CreditsMenu, HMML_VideoMetaData *Metad
 | 
			
		|||
    }
 | 
			
		||||
    FreeBook(&Credits);
 | 
			
		||||
 | 
			
		||||
    // NOTE(matt):  As we only cite the speaker when there are a multiple of them, we only need to SortAndAbbreviateSpeakers()
 | 
			
		||||
    //              in the same situation
 | 
			
		||||
    // NOTE(matt):  As we only cite the speaker when there are a multiple of them, we only
 | 
			
		||||
    //              need to SortSpeakersAndPickAbbreviationScheme() in the same situation
 | 
			
		||||
    if(Speakers->Speakers.ItemCount > 1)
 | 
			
		||||
    {
 | 
			
		||||
        SortAndAbbreviateSpeakers(Speakers);
 | 
			
		||||
        SortSpeakersAndPickAbbreviationScheme(Speakers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(CreditsMenu->Ptr > CreditsMenu->Location)
 | 
			
		||||
| 
						 | 
				
			
			@ -10093,7 +10065,6 @@ InitSpeakers()
 | 
			
		|||
{
 | 
			
		||||
    speakers Result = {};
 | 
			
		||||
    Result.Speakers = InitBook(sizeof(speaker), 4);
 | 
			
		||||
    Result.Abbreviations = InitBookOfStrings(64);
 | 
			
		||||
    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
 | 
			
		||||
            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,
 | 
			
		||||
                        "<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.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;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -16084,7 +16063,6 @@ InsertProjectIntoDB(project_generations *G, db_block_projects **Block, db_header
 | 
			
		|||
    uint64_t Byte = 0;
 | 
			
		||||
 | 
			
		||||
    OpenFileForWriting(&DB.Metadata.File);
 | 
			
		||||
    WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Block);
 | 
			
		||||
 | 
			
		||||
    uint64_t PPos;
 | 
			
		||||
    if(GotBlock)
 | 
			
		||||
| 
						 | 
				
			
			@ -17258,11 +17236,8 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke
 | 
			
		|||
                    } break;
 | 
			
		||||
                case WT_CONFIG:
 | 
			
		||||
                    {
 | 
			
		||||
                        if(Config)
 | 
			
		||||
                        {
 | 
			
		||||
                            DiscardAllAndFreeConfig();
 | 
			
		||||
                            PushWatchHandle(ConfigPath, EXT_NULL, WT_CONFIG, 0, 0);
 | 
			
		||||
                        }
 | 
			
		||||
                        DiscardAllAndFreeConfig();
 | 
			
		||||
                        PushWatchHandle(ConfigPath, EXT_NULL, WT_CONFIG, 0, 0);
 | 
			
		||||
 | 
			
		||||
                        ParseAndEitherPrintConfigOrInitAll(ConfigPath, TokensList, N, CollationBuffers, BespokeTemplate);
 | 
			
		||||
                    } 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.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(); };
 | 
			
		||||
 | 
			
		||||
        CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -480,11 +480,21 @@ typedef struct
 | 
			
		|||
    bool Hidden;
 | 
			
		||||
} medium;
 | 
			
		||||
 | 
			
		||||
typedef enum
 | 
			
		||||
{
 | 
			
		||||
    AS_INITIAL,
 | 
			
		||||
    AS_GIVEN_OR_NICKNAME,
 | 
			
		||||
    AS_DOTTED_INITIAL_AND_SURNAME,
 | 
			
		||||
    AS_NONE,
 | 
			
		||||
    AS_COUNT
 | 
			
		||||
} abbreviation_scheme;
 | 
			
		||||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
    string ID;
 | 
			
		||||
    string Name;
 | 
			
		||||
    // TODO(matt):  string SortName;
 | 
			
		||||
    string Abbreviations[AS_COUNT];
 | 
			
		||||
    string QuoteUsername;
 | 
			
		||||
    string Homepage;
 | 
			
		||||
    _memory_book(support) Support;
 | 
			
		||||
| 
						 | 
				
			
			@ -979,6 +989,9 @@ InitTypeSpecs(void)
 | 
			
		|||
 | 
			
		||||
    config_type_spec *Person = PushTypeSpec(&Result, IDENT_PERSON, FALSE);
 | 
			
		||||
    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_QUOTE_USERNAME, TRUE);
 | 
			
		||||
    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
 | 
			
		||||
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);
 | 
			
		||||
    person *This = MakeSpaceInBook(&C->Person);
 | 
			
		||||
    This->ID = ResolveString(C, E, PersonTree, &PersonTree->ID, FALSE);
 | 
			
		||||
    bool NamingError = FALSE;
 | 
			
		||||
    for(int i = 0; i < PersonTree->Pairs.ItemCount; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        config_pair *Pair = GetPlaceInBook(&PersonTree->Pairs, i);
 | 
			
		||||
        if(IDENT_NAME == Pair->Key)
 | 
			
		||||
        {
 | 
			
		||||
            This->Name = ResolveString(C, E, PersonTree, Pair, FALSE);
 | 
			
		||||
            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)
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
    {
 | 
			
		||||
        This->QuoteUsername = This->ID;
 | 
			
		||||
| 
						 | 
				
			
			@ -4113,6 +4382,9 @@ GetRowsRequiredForPersonInfo(typography *T, person *P)
 | 
			
		|||
    uint8_t RowsRequired = 0;
 | 
			
		||||
 | 
			
		||||
    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; }
 | 
			
		||||
    for(int i = 0; i < P->Support.ItemCount; ++i)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -4150,6 +4422,27 @@ PrintPerson(person *P, typography *Typography)
 | 
			
		|||
        --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)
 | 
			
		||||
    {
 | 
			
		||||
        fprintf(stderr, "%s ", RowsRequired == 1 ? Typography->LowerLeft : Typography->Vertical);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue