#if 0 ctime -begin ${0%.*}.ctm gcc -g -Wall -fsanitize=address -std=c99 -pipe $0 -o ${0%.*} hmml.a ctime -end ${0%.*}.ctm exit #endif #define DEBUG 0 typedef unsigned int bool; #define TRUE 1 #define FALSE 0 #include // NOTE(matt): varargs #include // NOTE(matt): printf, sprintf, vsprintf, fprintf, perror #include // NOTE(matt): calloc, malloc, free #include "hmmlib.h" //#include "config.h" // TODO(matt): Implement config.h #define Kilobytes(Bytes) Bytes << 10 #define Megabytes(Bytes) Bytes << 20 enum { EDITION_SINGLE = 0, EDITION_PROJECT = 1, EDITION_NETWORK = 2 } EDITION; typedef struct { char *Location; char *Ptr; char *ID; int Size; } buffer; // TODO(matt): Consider putting the ref_info and quote_info into linked lists on the heap, just to avoid all the hardcoded sizes typedef struct { char Date[32]; char Text[512]; } quote_info; typedef struct { char Timecode[8]; int Identifier; } identifier; #define REF_MAX_IDENTIFIER 32 typedef struct { char RefTitle[620]; char ID[512]; char URL[512]; char Source[256]; identifier Identifier[REF_MAX_IDENTIFIER]; int IdentifierCount; } ref_info; typedef struct { char Marker[32]; char WrittenText[32]; } category_info; // TODO(matt): Parse this stuff out of a config file char *Credentials[ ][5] = { { "Miblo", "Matt Mascarenhas", "http://miblodelcarpio.co.uk", "cinera_icon_patreon.png", "https://patreon.com/miblo"}, { "miotatsu", "Mio Iwakura", "http://riscy.tv/", "cinera_icon_patreon.png", "https://patreon.com/miotatsu"}, { "nothings", "Sean Barrett", "https://nothings.org/", "", ""}, { "cmuratori", "Casey Muratori", "https://handmadehero.org", "cinera_icon_patreon.png", "https://patreon.com/cmuratori"}, { "fierydrake", "Mike Tunnicliffe", "", "", ""}, { "abnercoimbre", "Abner Coimbre", "https://handmade.network/m/abnercoimbre", "cinera_icon_patreon.png", "https://patreon.com/handmade_dev"}, }; #define EDITION EDITION_SINGLE #define ArrayCount(A) sizeof(A)/sizeof(*(A)) void ClaimBuffer(char *MemoryArena, int *ClaimedMemory, buffer *Buffer, char *ID, int Size) { Buffer->Location = MemoryArena + *ClaimedMemory; Buffer->Size = Size; Buffer->ID = ID; *ClaimedMemory += Buffer->Size; *Buffer->Location = '\0'; Buffer->Ptr = Buffer->Location; #if DEBUG printf(" Claimed: %s: %d\n" " Total ClaimedMemory: %d\n\n", Buffer->ID, Buffer->Size, *ClaimedMemory); #endif } void DeclaimBuffer(buffer *Buffer, int *ClaimedMemory) { *Buffer->Location = '\0'; *ClaimedMemory -= Buffer->Size; #if DEBUG printf("Declaimed: %s\n" " Total ClaimedMemory: %d\n\n", Buffer->ID, *ClaimedMemory); #endif } int TimecodeToSeconds(char *Timecode) { int HMS[3] = { 0, 0, 0 }; // 0 == Seconds; 1 == Minutes; 2 == Hours int Colons = 0; while(*Timecode) { //if((*Timecode < '0' || *Timecode > '9') && *Timecode != ':') { return FALSE; } if(*Timecode == ':') { ++Colons; //if(Colons > 2) { return FALSE; } for(int i = 0; i < Colons; ++i) { HMS[Colons - i] = HMS[Colons - (i + 1)]; } HMS[0] = 0; } else { HMS[0] = HMS[0] * 10 + *Timecode - '0'; } ++Timecode; } //if(HMS[0] > 59 || HMS[1] > 59 || Timecode[-1] == ':') { return FALSE; } return HMS[2] * 60 * 60 + HMS[1] * 60 + HMS[0]; } void CopyBuffer(buffer *Dest, buffer *Src) { Src->Ptr = Src->Location; while(*Src->Ptr) { // TODO(matt) { if(Dest->Ptr - Dest->Location >= Dest->Size) { fprintf(stderr, "CopyBuffer: %s cannot accommodate %s\n", Dest->ID, Src->ID); __asm__("int3"); } } *Dest->Ptr++ = *Src->Ptr++; } } __attribute__ ((format (printf, 2, 3))) void CopyString(char Dest[], char *Format, ...) { va_list Args; va_start(Args, Format); vsprintf(Dest, Format, Args); va_end(Args); } int CopyStringNoFormat(char *Dest, char *String) { int Length = 0; while(*String) { *Dest++ = *String++; ++Length; } *Dest = '\0'; return Length; } int StringLength(char *String) { int i = 0; while(String[i]) { ++i; } return i; } __attribute__ ((format (printf, 2, 3))) void CopyStringToBuffer(buffer *Dest, char *Format, ...) { va_list Args; va_start(Args, Format); int Length = vsnprintf(Dest->Ptr, Dest->Size - (Dest->Ptr - Dest->Location), Format, Args); va_end(Args); // TODO(matt): { if(Length + (Dest->Ptr - Dest->Location) >= Dest->Size) { fprintf(stderr, "CopyStringToBuffer: %s cannot accommodate %d-character string:\n" "\n" "%s\n", Dest->ID, Length, Format); __asm__("int3"); } } Dest->Ptr += Length; } void CopyStringToBufferHTMLSafe(buffer *Dest, char *String) { while(*String) { if(Dest->Ptr - Dest->Location >= Dest->Size) { fprintf(stderr, "CopyStringToBufferHTMLSafe: %s cannot accommodate %d-character string\n", Dest->ID, StringLength(String)); __asm__("int3"); } switch(*String) { case '<': CopyStringToBuffer(Dest, "<"); String++; break; case '>': CopyStringToBuffer(Dest, ">"); String++; break; case '&': CopyStringToBuffer(Dest, "&"); String++; break; case '\"': CopyStringToBuffer(Dest, """); String++; break; case '\'': CopyStringToBuffer(Dest, "'"); String++; break; default: *Dest->Ptr++ = *String++; break; } } } void CopyStringToBufferCSVSafe(buffer *Dest, char *String) { while(*String) { if(Dest->Ptr - Dest->Location >= Dest->Size) { fprintf(stderr, "CopyStringToBufferHTMLSafe: %s cannot accommodate %d-character string\n", Dest->ID, StringLength(String)); __asm__("int3"); } switch(*String) { case '<': CopyStringToBuffer(Dest, "<"); String++; break; case '>': CopyStringToBuffer(Dest, ">"); String++; break; case '&': CopyStringToBuffer(Dest, "&"); String++; break; case '\"': CopyStringToBuffer(Dest, """); String += 2; break; case '\'': CopyStringToBuffer(Dest, "'"); String++; break; default: *Dest->Ptr++ = *String++; break; } } } int StringsDiffer(char *A, char *B) // NOTE(matt): Two null-terminated strings { while(*A && *B && *A == *B) { ++A, ++B; } return *A - *B; } bool StringsDifferT(char *A, // NOTE(matt): Null-terminated string char *B, // NOTE(matt): Not null-terminated string (e.g. one mid-buffer) char Terminator // NOTE(matt): Caller definable terminator. Pass 0 to only match on the extent of A ) { int ALength = StringLength(A); int i = 0; while(i < ALength && A[i] && A[i] == B[i]) { ++i; } if((!Terminator && !A[i] && ALength == i) || (!A[i] && ALength == i && (B[i] == Terminator))) { return FALSE; } else { return TRUE; } } typedef struct { unsigned int Hue:16; unsigned int Saturation:8; unsigned int Lightness:8; } hsl_colour; hsl_colour CharToColour(char Char) { hsl_colour Colour; if(Char >= 'a' && Char <= 'z') { Colour.Hue = (((float)Char - 'a') / ('z' - 'a') * 360); Colour.Saturation = (((float)Char - 'a') / ('z' - 'a') * 26 + 74); } else if(Char >= 'A' && Char <= 'Z') { Colour.Hue = (((float)Char - 'A') / ('Z' - 'A') * 360); Colour.Saturation = (((float)Char - 'A') / ('Z' - 'A') * 26 + 74); } else if(Char >= '0' && Char <= '9') { Colour.Hue = (((float)Char - '0') / ('9' - '0') * 360); Colour.Saturation = (((float)Char - '0') / ('9' - '0') * 26 + 74); } else { Colour.Hue = 180; Colour.Saturation = 50; } return Colour; } hsl_colour * StringToColourHash(hsl_colour *Colour, char *String) { Colour->Hue = 0; Colour->Saturation = 0; Colour->Lightness = 26; int i; for(i = 0; String[i]; ++i) { Colour->Hue += CharToColour(String[i]).Hue; Colour->Saturation += CharToColour(String[i]).Saturation; } Colour->Hue = Colour->Hue % 360; Colour->Saturation = Colour->Saturation % 26 + 74; return(Colour); } char * SanitisePunctuation(char *String) { char *Ptr = String; while(*Ptr) { if(*Ptr == ' ') { *Ptr = '_'; } if((*Ptr < '0' || *Ptr > '9') && (*Ptr < 'a' || *Ptr > 'z') && (*Ptr < 'A' || *Ptr > 'Z')) { *Ptr = '-'; } ++Ptr; } return String; } int BuildCredits(buffer *CreditsMenu, buffer *HostInfo, buffer *AnnotatorInfo, bool *HasCreditsMenu, char *Host, char *Annotator) { // TODO(matt): Handle co-hosts and guests bool FoundHost = FALSE; bool FoundAnnotator = FALSE; for(int CredentialIndex = 0; CredentialIndex < ArrayCount(Credentials); ++CredentialIndex) { if(!StringsDiffer(Host, Credentials[CredentialIndex][0])) { FoundHost = TRUE; // TODO(matt): Check if this is actually necessary... CopyStringToBuffer(HostInfo, " \n"); if(*Credentials[CredentialIndex][2]) { CopyStringToBuffer(HostInfo, " \n" "
Host
\n" "
%s
\n" "
\n", Credentials[CredentialIndex][2], Credentials[CredentialIndex][1]); } else { CopyStringToBuffer(HostInfo, "
\n" "
Host
\n" "
%s
\n" "
\n", Credentials[CredentialIndex][1]); } if(*Credentials[CredentialIndex][4] && *Credentials[CredentialIndex][3]) { CopyStringToBuffer(HostInfo, " \n", Credentials[CredentialIndex][4], Credentials[CredentialIndex][3]); } CopyStringToBuffer(HostInfo, "
\n"); } if(!StringsDiffer(Annotator, Credentials[CredentialIndex][0])) { FoundAnnotator = TRUE; // TODO(matt): Check if this is actually necessary... CopyStringToBuffer(AnnotatorInfo, " \n"); if(*Credentials[CredentialIndex][2]) { CopyStringToBuffer(AnnotatorInfo, " \n" "
Annotator
\n" "
%s
\n" "
\n", Credentials[CredentialIndex][2], Credentials[CredentialIndex][1]); } else { CopyStringToBuffer(AnnotatorInfo, "
\n" "
Annotator
\n" "
%s
\n" "
\n", Credentials[CredentialIndex][1]); } if(*Credentials[CredentialIndex][4] && *Credentials[CredentialIndex][3]) { CopyStringToBuffer(AnnotatorInfo, " \n", Credentials[CredentialIndex][4], Credentials[CredentialIndex][3]); } CopyStringToBuffer(AnnotatorInfo, "
\n"); } } if(FoundHost || FoundAnnotator) { CopyStringToBuffer(CreditsMenu, "
\n" "
\n" " Credits\n" "
\n"); if(FoundHost) { CopyBuffer(CreditsMenu, HostInfo); } if(FoundAnnotator) { CopyBuffer(CreditsMenu, AnnotatorInfo); } CopyStringToBuffer(CreditsMenu, "
\n" "
\n"); } else { return 1; } *HasCreditsMenu = TRUE; return 0; } int BuildReference(ref_info *ReferencesArray, int RefIdentifier, int UniqueRefs, HMML_Reference Ref, HMML_Annotation Anno) { if(Ref.url && Ref.title && Ref.author && Ref.publisher && Ref.isbn) { CopyString(ReferencesArray[UniqueRefs].ID, Ref.isbn); CopyString(ReferencesArray[UniqueRefs].Source, "%s (%s)", Ref.author, Ref.publisher); CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title); CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url); } else if(Ref.page && Ref.url && Ref.title) { CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url); CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, Ref.title); CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.page); CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url); } else if(Ref.site && Ref.page && Ref.url) { CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url); CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, Ref.site); CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.page); CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url); } else if(Ref.site && Ref.url && Ref.title) { CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url); CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, Ref.site); CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title); CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url); } else if(Ref.title && Ref.author && Ref.isbn) { CopyString(ReferencesArray[UniqueRefs].ID, Ref.isbn); CopyString(ReferencesArray[UniqueRefs].Source, Ref.author); CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title); //TODO(matt): Look into finding the best ISBN searcher the web has to offer CopyString(ReferencesArray[UniqueRefs].URL, "http://www.openisbn.com/isbn/%s", Ref.isbn); } else if(Ref.url && Ref.article && Ref.author) { CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url); CopyString(ReferencesArray[UniqueRefs].Source, Ref.author); CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.article); CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url); } else if(Ref.url && Ref.title && Ref.author) { CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url); CopyString(ReferencesArray[UniqueRefs].Source, Ref.author); CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title); CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url); } else if(Ref.url && Ref.title) { CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url); CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title); CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url); } else if(Ref.site && Ref.url) { CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url); CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.site); CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url); } else { return 1; } CopyString(ReferencesArray[UniqueRefs].Identifier[ReferencesArray[UniqueRefs].IdentifierCount].Timecode, Anno.time); ReferencesArray[UniqueRefs].Identifier[ReferencesArray[UniqueRefs].IdentifierCount].Identifier = RefIdentifier; return 0; } char *CategoryMedium[][3] = { // medium icon written name { "authored", "🗪", "Chat Comment"}, // TODO(matt): Conditionally handle Chat vs Guest Comments { "blackboard", "🖌", "Blackboard"}, { "default", "🖮", "Programming"}, // TODO(matt): Potentially make this configurable per project { "experience", "🍷", "Experience"}, { "owl", "🦉", "Owl of Shame"}, { "rant", "💢", "Rant"}, { "research", "📖", "Research"}, { "run", "🏃", "In-Game"}, // TODO(matt): Potentially make this configurable per project { "trivia", "🎲", "Trivia"}, }; void BuildFilter(category_info *TopicsArray, int *UniqueTopics, category_info *MediaArray, int *UniqueMedia, char *Marker) { bool IsMedium = FALSE; int i = 0; for(i = 0; i < ArrayCount(CategoryMedium); ++i) { if(!StringsDiffer(CategoryMedium[i][0], Marker)) { IsMedium = TRUE; break; } } int Offset; if(IsMedium) { int j = 0; for(j = 0; j < *UniqueMedia; ++j) { if(!StringsDiffer(CategoryMedium[i][0], MediaArray[j].Marker)) { return; } if((Offset = StringsDiffer(CategoryMedium[i][2], MediaArray[j].WrittenText)) < 0) { int k; for(k = *UniqueMedia; k > j; --k) { CopyString(MediaArray[k].Marker, MediaArray[k-1].Marker); CopyString(MediaArray[k].WrittenText, MediaArray[k-1].WrittenText); } CopyString(MediaArray[k].Marker, CategoryMedium[i][0]); CopyString(MediaArray[k].WrittenText, CategoryMedium[i][2]); break; } } if(j == *UniqueMedia) { CopyString(MediaArray[j].Marker, CategoryMedium[i][0]); CopyString(MediaArray[j].WrittenText, CategoryMedium[i][2]); } ++*UniqueMedia; return; } else { int i = 0; for(i = 0; i < *UniqueTopics; ++i) { if(!StringsDiffer(Marker, TopicsArray[i].Marker)) { return; } if((Offset = StringsDiffer(Marker, TopicsArray[i].Marker)) < 0) { int j; for(j = *UniqueTopics; j > i; --j) { CopyString(TopicsArray[j].Marker, TopicsArray[j-1].Marker); } CopyString(TopicsArray[j].Marker, Marker); break; } } if(i == *UniqueTopics) { CopyString(TopicsArray[i].Marker, Marker); } ++*UniqueTopics; return; } } void BuildCategories(buffer *AnnotationClass, buffer *Category, int *MarkerIndex, bool *HasCategory, bool *HasMedium, char *Marker) { // NOTE(matt): This guy could also sort, so that the dots appear in a consistent order in the annotations // If so, the Category buffer would have to only contain the category names and no more until collation time // BuildCategories() would have to parse the Category.Location out to an array, sort that array and write it back in // The code in the "annotation loop" would then have to write both the head and tail of the category stuff for(int i = 0; i < ArrayCount(CategoryMedium); ++i) { if(!StringsDiffer(CategoryMedium[i][0], Marker)) { CopyStringToBuffer(AnnotationClass, " %s", SanitisePunctuation(Marker)); *HasMedium = TRUE; ++*MarkerIndex; return; } } if(*HasCategory == FALSE) { CopyStringToBuffer(Category, ""); *HasCategory = TRUE; } // NOTE(matt): Iterate through the Category->Location looking for "Marker", and bail if we find it char *Ptr = Category->Location; bool Found = FALSE; while(*Ptr) { if(*Ptr == '\"') { ++Ptr; if(!StringsDifferT(SanitisePunctuation(Marker), Ptr, ' ')) { Found = TRUE; break; } else { while(*Ptr != '\"') { ++Ptr; } } } ++Ptr; } if(Found == FALSE) { CopyStringToBuffer(Category, "
", SanitisePunctuation(Marker), SanitisePunctuation(Marker)); CopyStringToBuffer(AnnotationClass, " cat_%s", SanitisePunctuation(Marker)); } ++*MarkerIndex; return; } int StringToInt(char *String) { int Result = 0; while(*String) { Result = Result * 10 + (*String - '0'); ++String; } return Result; } int BuildQuote(quote_info *Info, char *Speaker, int ID) { // TODO(matt): Pull these paths from the config // Also notable that there are different paths for different projects char *QuoteDir = "/home/matt/git/GitHub/insofaras/25fc16d58a297a486334"; char Path[255] = {0}; sprintf(Path, "%s/#%s", QuoteDir, Speaker); FILE *File; if(!(File = fopen(Path, "r"))) { perror("hmml_to_html"); return 1; } fseek(File, 0, SEEK_END); int Length = ftell(File); fseek(File, 0, SEEK_SET); char *Buffer; if(!(Buffer = malloc(Length))) { perror("hmml_to_html"); } fread(Buffer, Length, 1, File); fclose(File); // TODO(matt): Search the quote store in reverse char *InPtr = Buffer; while(InPtr - Buffer < Length) { char InID[4] = { 0 }; char *OutPtr = InID; while(*InPtr != ',') { *OutPtr++ = *InPtr++; } *OutPtr = '\0'; if(StringToInt(InID) == ID) { InPtr += 2; OutPtr = Info->Date; while(*InPtr != '"') { *OutPtr++ = *InPtr++; } *OutPtr = '\0'; InPtr += 3; OutPtr = Info->Text; while(*InPtr != '\n') { if(*InPtr == '\\') { ++InPtr; } *OutPtr++ = *InPtr++; } *--OutPtr = '\0'; free(Buffer); return 0; } else { while(*InPtr != '\n') { ++InPtr; } ++InPtr; } } free(Buffer); return 1; } void GenerateTopicColours(buffer *Colour, char *Topic) { for(int i = 0; i < ArrayCount(CategoryMedium); ++i) { if(!StringsDiffer(Topic, CategoryMedium[i][0])) { return; } } FILE *TopicsFile; char *TopicsBuffer; // TODO(matt): Consider (optionally) pulling this path from the config if((TopicsFile = fopen("cinera_topics.css", "a+"))) { fseek(TopicsFile, 0, SEEK_END); int TopicsLength = ftell(TopicsFile); fseek(TopicsFile, 0, SEEK_SET); // TODO(matt): May this not just ClaimBuffer (if I can figure out how)? if(!(TopicsBuffer = malloc(TopicsLength))) { perror("GenerateTopicColours"); return; } fread(TopicsBuffer, TopicsLength, 1, TopicsFile); char *TopicsPtr = TopicsBuffer; while(TopicsPtr - TopicsBuffer < TopicsLength) { TopicsPtr += StringLength(".category."); if(!StringsDifferT(SanitisePunctuation(Topic), TopicsPtr, ' ')) { free(TopicsBuffer); fclose(TopicsFile); return; } while(TopicsPtr - TopicsBuffer < TopicsLength && *TopicsPtr != '\n') { ++TopicsPtr; } ++TopicsPtr; } hsl_colour Colour; StringToColourHash(&Colour, Topic); fprintf(TopicsFile, ".category.%s { border: 1px solid hsl(%d, %d%%, %d%%); background: hsl(%d, %d%%, %d%%); }\n", SanitisePunctuation(Topic), Colour.Hue, Colour.Saturation, Colour.Lightness, Colour.Hue, Colour.Saturation, Colour.Lightness); fclose(TopicsFile); free(TopicsBuffer); } else { perror("GenerateTopicColours"); return; } } #define CONFIG 0 #if CONFIG typedef struct { char *Username; char *Display_Name; char *Homepage; char *Funding_Platform; char *Funding_Username; unsigned int Index; } credentials; typedef struct { credentials Credentials; } config; int ParseConfig(buffer *Buffer, char *Username) { /* Essentially, I want to pass a Username to this, and have it write the credentials into the Config buffer Let's start by just grabbing the stuff and printing it out */ // TODO(matt): Actually figure out the "standard" config location char Config_Location[255]; if(getenv("XDG_CONFIG_HOME")) { sprintf(Config_Location, "%s/hmml.conf", getenv("XDG_CONFIG_HOME")); } else if(getenv("HOME")) { sprintf(Config_Location, "%s/.config/hmml.conf", getenv("HOME")); } else { fprintf(stderr, "Config file location not set"); return 1; } FILE *InFile; if(!(InFile = fopen(Config_Location, "r"))) { perror(Config_Location); return 2; } printf("Reading: %s\n", Config_Location); fseek(InFile, 0, SEEK_END); int InSize = ftell(InFile); fseek(InFile, 0, SEEK_SET); char *InBuffer; //config Config = { 0 }; if(!(InBuffer = malloc(InSize))) { perror("ParseConfig"); return 3; } fread(InBuffer, InSize, 1, InFile); fclose(InFile); char *InPtr = InBuffer; char OutBuffer[256]; //char *OutPtr = Config.Credentials.Display_Name; char *OutPtr = OutBuffer; bool Quoted = FALSE; bool FoundCredentials, ParsingUsername = FALSE; unsigned int ScopeDepth = 0; while(InPtr - InBuffer < InSize) { switch(*InPtr) { case '#': { if(!Quoted) { printf(" We are commenting\n"); while(InPtr - InBuffer < InSize && *InPtr != '\n') { ++InPtr; } ++InPtr; while(InPtr - InBuffer < InSize && (*InPtr == ' ' || *InPtr == '\n')) { ++InPtr; } } else { *OutPtr++ = *InPtr++; } break; } case '{': { if(!Quoted) { ++ScopeDepth; ++InPtr; while(*InPtr == '\n' || *InPtr == ' ') { ++InPtr; } printf(" We have entered a scope\n"); } else { *OutPtr++ = *InPtr++; } break; } case '}': { if(!Quoted) { --ScopeDepth; ++InPtr; printf(" We have left a scope\n"); } else { *OutPtr++ = *InPtr++; } break; } #if 1 case ' ': { if(!Quoted) { ++InPtr; *OutPtr = '\0'; OutPtr = OutBuffer; printf("%s\n", OutBuffer); // TODO(matt): Switch on the OutBuffer? I have a feeling that isn't actually possible, though if(!StringsDiffer("credentials", OutBuffer)) { FoundCredentials = TRUE; printf(" We have found the credentials block\n"); } if(ParsingUsername) { printf(" The username is %s\n", OutBuffer); ParsingUsername = FALSE; } if(FoundCredentials && (!StringsDiffer("username", OutBuffer))) { ParsingUsername = TRUE; printf(" We have found the username\n"); } } else { *OutPtr++ = *InPtr++; } break; } #endif case '"': { if(!Quoted) { Quoted = TRUE; printf(" We are quoting!\n"); } else { Quoted = FALSE; printf(" We are no longer quoting!\n"); } ++InPtr; break; } case ';': { if(!Quoted) { printf(" We have reached the end of a setting\n"); ++InPtr; } else { *OutPtr++ = *InPtr++; } } case '\n': { if(!Quoted) { if(InPtr - InBuffer < InSize) { *OutPtr = '\0'; OutPtr = OutBuffer; // TODO(matt) if(!StringsDiffer("credentials", OutBuffer)) { FoundCredentials = TRUE; printf(" We have found the credentials block\n"); } if(ParsingUsername) { printf(" The username is %s\n", OutBuffer); ParsingUsername = FALSE; } if(FoundCredentials && (!StringsDiffer("username", OutBuffer))) { ParsingUsername = TRUE; printf(" We have found the username\n"); } printf("%s\n", OutBuffer); ++InPtr; while(InPtr - InBuffer < InSize && *InPtr == ' ') // NOTE(matt): Skip indentation whitespace { ++InPtr; } } } else { *OutPtr++ = *InPtr++; } if(InPtr - InBuffer == InSize) { printf(" We have reached the EOF\n"); } break; } default: { *OutPtr++ = *InPtr++; break; } } } #if 0 while(InPtr - InBuffer < InSize) { while(*InPtr != '\n') { *OutPtr++ = *InPtr++; } *OutPtr = '\0'; printf("%s\n", Config.Credentials.Display_Name); } #endif free(InBuffer); // Reading from the config file, parsing it inline (on the stack) and writing into the buffer *Config return 0; } #endif int main(int ArgC, char **Args) { if(ArgC < 2) { fprintf(stderr, "Usage: %s filename(s)\n", Args[0]); return 1; } // NOTE(matt): Init MemoryArena char *MemoryArena; int ArenaSize = Megabytes(4); if(!(MemoryArena = calloc(ArenaSize, 1))) { perror(Args[0]); return 1; } int ClaimedMemory = 0; // NOTE(matt): Setup buffers and ptrs char *InPtr; #if CONFIG buffer Config; #endif // NOTE(matt): Tree structure of buffer dependencies // Master // Includes // Menus // QuoteMenu // ReferenceMenu // FilterMenu // FilterTopics // FilterMedia // Player // Colour // Annotation // AnnotationHeader // AnnotationClass // AnnotationData // Text // Category // Script // FilterState buffer Master; buffer Includes; buffer Menus; buffer QuoteMenu; buffer ReferenceMenu; buffer FilterMenu; buffer FilterTopics; buffer FilterMedia; buffer CreditsMenu; buffer HostInfo; buffer AnnotatorInfo; buffer Player; buffer Colour; buffer Annotation; buffer AnnotationHeader; buffer AnnotationClass; buffer AnnotationData; buffer Text; buffer Category; buffer Script; buffer FilterState; for(int FileIndex = 1; FileIndex < ArgC; ++FileIndex) { FILE *InFile; if(!(InFile = fopen(Args[FileIndex], "r"))) { perror(Args[0]); free(MemoryArena); return 1; } #if CONFIG ClaimBuffer(MemoryArena, &ClaimedMemory, &Config, "Config", Kilobytes(1)); #endif HMML_Output HMML = hmml_parse_file(InFile); fclose(InFile); if(HMML.well_formed) { #if DEBUG printf( "================================================================================\n" "%s\n" "================================================================================\n", Args[FileIndex]); #endif // NOTE(matt): Tree structure of "global" buffer dependencies // Master // Includes // Menus // QuoteMenu // ReferenceMenu // FilterMenu // FilterTopics // FilterMedia // CreditsMenu // Player // Colour // Annotation // Script // FilterState ClaimBuffer(MemoryArena, &ClaimedMemory, &Master, "Master", Kilobytes(512)); ClaimBuffer(MemoryArena, &ClaimedMemory, &Includes, "Includes", Kilobytes(1)); ClaimBuffer(MemoryArena, &ClaimedMemory, &Menus, "Menus", Kilobytes(24)); ClaimBuffer(MemoryArena, &ClaimedMemory, &QuoteMenu, "QuoteMenu", Kilobytes(16)); ClaimBuffer(MemoryArena, &ClaimedMemory, &ReferenceMenu, "ReferenceMenu", Kilobytes(16)); ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterMenu, "FilterMenu", Kilobytes(16)); ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterTopics, "FilterTopics", Kilobytes(8)); ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterMedia, "FilterMedia", Kilobytes(8)); ClaimBuffer(MemoryArena, &ClaimedMemory, &CreditsMenu, "CreditsMenu", Kilobytes(8)); ClaimBuffer(MemoryArena, &ClaimedMemory, &HostInfo, "HostInfo", Kilobytes(1)); ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotatorInfo, "AnnotatorInfo", Kilobytes(1)); ClaimBuffer(MemoryArena, &ClaimedMemory, &Player, "Player", Kilobytes(256)); ClaimBuffer(MemoryArena, &ClaimedMemory, &Colour, "Colour", 32); ClaimBuffer(MemoryArena, &ClaimedMemory, &Annotation, "Annotation", Kilobytes(8)); ClaimBuffer(MemoryArena, &ClaimedMemory, &Script, "Script", Kilobytes(8)); ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterState, "FilterState", Kilobytes(4)); ref_info ReferencesArray[200] = { 0 }; category_info TopicsArray[56] = { 0 }; category_info MediaArray[8] = { 0 }; bool HasQuoteMenu = FALSE; bool HasReferenceMenu = FALSE; bool HasFilterMenu = FALSE; bool HasCreditsMenu = FALSE; int QuoteIdentifier = 0x3b1; int RefIdentifier = 1; int UniqueRefs = 0; int UniqueTopics = 0; int UniqueMedia = 0; CopyStringToBuffer(&Menus, "
\n" " ", HMML.metadata.project); CopyStringToBufferHTMLSafe(&Menus, HMML.metadata.title); CopyStringToBuffer(&Menus, "\n" " ⚠ Click here to regain focus ⚠\n"); CopyStringToBuffer(&Player, "
\n" "
\n" "
\n", HMML.metadata.id, HMML.metadata.project); // TODO(matt): Handle multiple annotators if(HMML.metadata.annotator_count > 0) { BuildCredits(&CreditsMenu, &HostInfo, &AnnotatorInfo, &HasCreditsMenu, HMML.metadata.member, HMML.metadata.annotators[0]); } else { fprintf(stderr, "%s: Missing annotator in [video] node\n", Args[FileIndex]); hmml_free(&HMML); free(MemoryArena); return 1; } #if DEBUG printf(" --- Entering Annotations Loop ---\n"); #endif for(int AnnotationIndex = 0; AnnotationIndex < HMML.annotation_count; ++AnnotationIndex) { #if DEBUG printf("%d\n", AnnotationIndex); #endif HMML_Annotation *Anno = HMML.annotations + AnnotationIndex; bool HasCategory = FALSE; bool HasMedium = FALSE; bool HasQuote = FALSE; bool HasReference = FALSE; quote_info QuoteInfo = { 0 }; // NOTE(matt): Tree structure of "annotation local" buffer dependencies // AnnotationHeader // AnnotationClass // AnnotationData // Text // Category ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotationHeader, "AnnotationHeader", 512); ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotationClass, "AnnotationClass", 256); ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotationData, "AnnotationData", 256); ClaimBuffer(MemoryArena, &ClaimedMemory, &Text, "Text", Kilobytes(4)); ClaimBuffer(MemoryArena, &ClaimedMemory, &Category, "Category", 512); CopyStringToBuffer(&AnnotationHeader, "
time)); CopyStringToBuffer(&AnnotationClass, " class=\"marker"); if(Anno->author) { if(!HasFilterMenu) { HasFilterMenu = TRUE; } BuildFilter(TopicsArray, &UniqueTopics, MediaArray, &UniqueMedia, "authored"); CopyStringToBuffer(&AnnotationClass, " authored"); hsl_colour AuthorColour; StringToColourHash(&AuthorColour, Anno->author); if(EDITION == EDITION_NETWORK) { fprintf(stderr, "%s:%d - TODO(matt): Implement author hoverbox\n", __FILE__, __LINE__); // NOTE(matt): We should get instructions on how to get this info in the config CopyStringToBuffer(&Text, "%s ", Anno->author, AuthorColour.Hue, AuthorColour.Saturation, AuthorColour.Lightness, AuthorColour.Hue, AuthorColour.Saturation, Anno->author); } else { CopyStringToBuffer(&Text, "%s ", AuthorColour.Hue, AuthorColour.Saturation, AuthorColour.Lightness, AuthorColour.Hue, AuthorColour.Saturation, Anno->author); } if(!HasMedium) { HasMedium = TRUE; } } InPtr = Anno->text; int MarkerIndex = 0, RefIndex = 0; while(*InPtr || RefIndex < Anno->reference_count) { if(MarkerIndex < Anno->marker_count && InPtr - Anno->text == Anno->markers[MarkerIndex].offset) { char *Readable = Anno->markers[MarkerIndex].parameter ? Anno->markers[MarkerIndex].parameter : Anno->markers[MarkerIndex].marker; if(Anno->markers[MarkerIndex].type == HMML_MEMBER) { hsl_colour MemberColour; StringToColourHash(&MemberColour, Anno->markers[MarkerIndex].marker); if(EDITION == EDITION_NETWORK) { fprintf(stderr, "%s:%d - TODO(matt): Implement member hoverbox\n", __FILE__, __LINE__); // NOTE(matt): We should get instructions on how to get this info in the config CopyStringToBuffer(&Text, "%.*s", Anno->markers[MarkerIndex].marker, MemberColour.Hue, MemberColour.Saturation, MemberColour.Lightness, MemberColour.Hue, MemberColour.Saturation, StringLength(Readable), InPtr); } else { CopyStringToBuffer(&Text, "%.*s", MemberColour.Hue, MemberColour.Saturation, MemberColour.Lightness, MemberColour.Hue, MemberColour.Saturation, StringLength(Readable), InPtr); } InPtr += StringLength(Readable); ++MarkerIndex; } else if(Anno->markers[MarkerIndex].type == HMML_PROJECT) { hsl_colour ProjectColour; StringToColourHash(&ProjectColour, Anno->markers[MarkerIndex].marker); if(EDITION == EDITION_NETWORK) { fprintf(stderr, "%s:%d - TODO(matt): Implement project hoverbox\n", __FILE__, __LINE__); // NOTE(matt): We should get instructions on how to get this info in the config CopyStringToBuffer(&Text, "%s", Anno->markers[MarkerIndex].marker, ProjectColour.Hue, ProjectColour.Saturation, ProjectColour.Lightness, ProjectColour.Hue, ProjectColour.Saturation, Readable); } else { CopyStringToBuffer(&Text, "%s", ProjectColour.Hue, ProjectColour.Saturation, ProjectColour.Lightness, ProjectColour.Hue, ProjectColour.Saturation, Readable); } InPtr += StringLength(Readable); ++MarkerIndex; } else if(Anno->markers[MarkerIndex].type == HMML_CATEGORY) { GenerateTopicColours(&Colour, Anno->markers[MarkerIndex].marker); // TODO(matt): Maybe stuff this into BuildCategories if(!HasFilterMenu) { HasFilterMenu = TRUE; } BuildFilter(TopicsArray, &UniqueTopics, MediaArray, &UniqueMedia, Anno->markers[MarkerIndex].marker); BuildCategories(&AnnotationClass, &Category, &MarkerIndex, &HasCategory, &HasMedium, Anno->markers[MarkerIndex].marker); } } if(RefIndex < Anno->reference_count && InPtr - Anno->text == Anno->references[RefIndex].offset) { HMML_Reference *CurrentRef = Anno->references + RefIndex; if(!HasReferenceMenu) { CopyStringToBuffer(&ReferenceMenu, "
\n" " References ▼\n" "
\n" "
\n"); if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, *CurrentRef, *Anno) == 1) { fprintf(stderr, "%s:%d: Cannot process new combination of reference info\n", Args[FileIndex], Anno->line); hmml_free(&HMML); free(MemoryArena); return 1; } ++ReferencesArray[RefIdentifier - 1].IdentifierCount; ++UniqueRefs; HasReferenceMenu = TRUE; } else { for(int i = 0; i < UniqueRefs; ++i) { if(CurrentRef->isbn) { if(!StringsDiffer(CurrentRef->isbn, ReferencesArray[i].ID)) { CopyString(ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Timecode, Anno->time); ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Identifier = RefIdentifier; ++ReferencesArray[i].IdentifierCount; goto AppendedIdentifier; } } else if(CurrentRef->url) { if(!StringsDiffer(CurrentRef->url, ReferencesArray[i].ID)) { CopyString(ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Timecode, Anno->time); ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Identifier = RefIdentifier; ++ReferencesArray[i].IdentifierCount; goto AppendedIdentifier; } } else { fprintf(stderr, "%s:%d: Reference must have an ISBN or URL\n", Args[FileIndex], Anno->line); hmml_free(&HMML); free(MemoryArena); return 1; } } if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, *CurrentRef, *Anno) == 1) { fprintf(stderr, "%s:%d: Cannot process new combination of reference info\n", Args[FileIndex], Anno->line); hmml_free(&HMML); free(MemoryArena); return 1; } ++ReferencesArray[UniqueRefs].IdentifierCount; ++UniqueRefs; } AppendedIdentifier: if(!HasReference) { if(CurrentRef->isbn) { CopyStringToBuffer(&AnnotationData, " data-ref=\"%s", CurrentRef->isbn); } else if(CurrentRef->url) { CopyStringToBuffer(&AnnotationData, " data-ref=\"%s", CurrentRef->url); } else { fprintf(stderr, "%s:%d: Reference must have an ISBN or URL\n", Args[FileIndex], Anno->line); hmml_free(&HMML); free(MemoryArena); return 1; } HasReference = TRUE; } else { if(CurrentRef->isbn) { CopyStringToBuffer(&AnnotationData, ",%s", CurrentRef->isbn); } else if(CurrentRef->url) { CopyStringToBuffer(&AnnotationData, ",%s", CurrentRef->url); } else { fprintf(stderr, "%s:%d: Reference must have an ISBN or URL", Args[FileIndex], Anno->line); hmml_free(&HMML); free(MemoryArena); return 1; } } if(RefIndex > 1 && Anno->references[RefIndex].offset == Anno->references[RefIndex-1].offset) { CopyStringToBuffer(&Text, ",%d", RefIdentifier); } else { CopyStringToBuffer(&Text, "%d", RefIdentifier); } ++RefIndex; ++RefIdentifier; } if(*InPtr) { switch(*InPtr) { case '<': CopyStringToBuffer(&Text, "<"); InPtr++; break; case '>': CopyStringToBuffer(&Text, ">"); InPtr++; break; case '&': CopyStringToBuffer(&Text, "&"); InPtr++; break; case '\"': CopyStringToBuffer(&Text, """); InPtr++; break; case '\'': CopyStringToBuffer(&Text, "'"); InPtr++; break; default: *Text.Ptr++ = *InPtr++; break; } } } if(Anno->is_quote) { if(!HasQuoteMenu) { CopyStringToBuffer(&QuoteMenu, "
\n" " Quotes ▼\n" "
\n" "
\n"); HasQuoteMenu = TRUE; } if(!HasReference) { CopyStringToBuffer(&AnnotationData, " data-ref=\"&#%d;", QuoteIdentifier); } else { CopyStringToBuffer(&AnnotationData, ",&#%d;", QuoteIdentifier); } HasQuote = TRUE; if(BuildQuote(&QuoteInfo, HMML.metadata.stream_username ? HMML.metadata.stream_username : HMML.metadata.member, Anno->quote.id) == 1) { fprintf(stderr, "%s:%d: Quote #%s %d not found! Consider pulling the latest quotes\n", Args[FileIndex], Anno->line, HMML.metadata.stream_username ? HMML.metadata.stream_username : HMML.metadata.member, Anno->quote.id); hmml_free(&HMML); free(MemoryArena); return 1; } CopyStringToBuffer(&QuoteMenu, " \n" " \n" "
Quote %d
\n" "
", QuoteIdentifier, Anno->quote.id); CopyStringToBufferCSVSafe(&QuoteMenu, QuoteInfo.Text); CopyStringToBuffer(&QuoteMenu, "
\n" " \n" "
\n" "
\n" " [&#%d;]%s\n" "
\n" "
\n", HMML.metadata.stream_username ? HMML.metadata.stream_username : HMML.metadata.member, QuoteInfo.Date, TimecodeToSeconds(Anno->time), QuoteIdentifier, Anno->time); if(!Anno->text[0]) { CopyStringToBuffer(&Text, "“"); CopyStringToBufferHTMLSafe(&Text, QuoteInfo.Text); CopyStringToBuffer(&Text, "”"); } CopyStringToBuffer(&Text, "&#%d;", QuoteIdentifier); ++QuoteIdentifier; } while(MarkerIndex < Anno->marker_count) { GenerateTopicColours(&Colour, Anno->markers[MarkerIndex].marker); // TODO(matt): Maybe stuff this into BuildCategories if(!HasFilterMenu) { HasFilterMenu = TRUE; } if(Anno->markers[MarkerIndex].marker) { BuildFilter(TopicsArray, &UniqueTopics, MediaArray, &UniqueMedia, Anno->markers[MarkerIndex].marker); } BuildCategories(&AnnotationClass, &Category, &MarkerIndex, &HasCategory, &HasMedium, Anno->markers[MarkerIndex].marker); } if(!HasMedium) { BuildFilter(TopicsArray, &UniqueTopics, MediaArray, &UniqueMedia, "default"); BuildCategories(&AnnotationClass, &Category, &MarkerIndex, &HasCategory, &HasMedium, "default"); } CopyStringToBuffer(&AnnotationClass, "\""); CopyBuffer(&AnnotationHeader, &AnnotationClass); if(HasQuote || HasReference) { CopyStringToBuffer(&AnnotationData, "\""); CopyBuffer(&AnnotationHeader, &AnnotationData); } CopyStringToBuffer(&AnnotationHeader, ">\n"); CopyBuffer(&Annotation, &AnnotationHeader); CopyStringToBuffer(&Annotation, "
%s", Anno->time); if(HasCategory) { CopyStringToBuffer(&Category, ""); CopyBuffer(&Text, &Category); } // NOTE(matt): This feels a bit janky... *Text.Ptr = '\0'; CopyBuffer(&Annotation, &Text); CopyStringToBuffer(&Annotation, "
\n" "
\n" "
%s", Anno->time); CopyBuffer(&Annotation, &Text); CopyStringToBuffer(&Annotation, "
\n" "
\n" "
\n" "
%s", Anno->time); CopyBuffer(&Annotation, &Text); CopyStringToBuffer(&Annotation, "
\n" "
\n" "
\n"); CopyBuffer(&Player, &Annotation); // NOTE(matt): Tree structure of "annotation local" buffer dependencies // Category // Text // AnnotationData // AnnotationClass // AnnotationHeader DeclaimBuffer(&Category, &ClaimedMemory); DeclaimBuffer(&Text, &ClaimedMemory); DeclaimBuffer(&AnnotationData, &ClaimedMemory); DeclaimBuffer(&AnnotationClass, &ClaimedMemory); DeclaimBuffer(&AnnotationHeader, &ClaimedMemory); Annotation.Ptr = Annotation.Location; } #if DEBUG printf(" --- End of Annotations Loop ---\n\n"); #endif if(HasQuoteMenu) { CopyStringToBuffer(&QuoteMenu, "
\n" "
\n"); CopyBuffer(&Menus, &QuoteMenu); } if(HasReferenceMenu) { for(int i = 0; i < UniqueRefs; ++i) { CopyStringToBuffer(&ReferenceMenu, " \n" " \n", ReferencesArray[i].ID, ReferencesArray[i].URL); if(*ReferencesArray[i].Source) { CopyStringToBuffer(&ReferenceMenu, "
"); CopyStringToBufferHTMLSafe(&ReferenceMenu, ReferencesArray[i].Source); CopyStringToBuffer(&ReferenceMenu, "
\n" "
"); CopyStringToBufferHTMLSafe(&ReferenceMenu, ReferencesArray[i].RefTitle); CopyStringToBuffer(&ReferenceMenu, "
\n"); } else { CopyStringToBuffer(&ReferenceMenu, "
"); CopyStringToBufferHTMLSafe(&ReferenceMenu, ReferencesArray[i].RefTitle); CopyStringToBuffer(&ReferenceMenu, "
\n"); } CopyStringToBuffer(&ReferenceMenu, "
\n"); for(int j = 0; j < ReferencesArray[i].IdentifierCount;) { CopyStringToBuffer(&ReferenceMenu, "
\n "); for(int k = 0; k < 3 && j < ReferencesArray[i].IdentifierCount; ++k, ++j) { CopyStringToBuffer(&ReferenceMenu, "[%d]%s", TimecodeToSeconds(ReferencesArray[i].Identifier[j].Timecode), ReferencesArray[i].Identifier[j].Identifier, ReferencesArray[i].Identifier[j].Timecode); } CopyStringToBuffer(&ReferenceMenu, "\n" "
\n"); } CopyStringToBuffer(&ReferenceMenu, "
\n"); } CopyStringToBuffer(&ReferenceMenu, "
\n" "
\n"); CopyBuffer(&Menus, &ReferenceMenu); } if(HasFilterMenu) { CopyStringToBuffer(&FilterState, " var filterState = {\n"); for(int i = 0; i < UniqueTopics; ++i) { CopyStringToBuffer(&FilterState, "\"%s\":\t{ \"type\": \"%s\",\t\"off\": false },\n", TopicsArray[i].Marker, "topic"); } for(int i = 0; i < UniqueMedia; ++i) { CopyStringToBuffer(&FilterState, "\"%s\":\t{ \"type\": \"%s\",\t\"off\": false },\n", MediaArray[i].Marker, "medium"); } CopyStringToBuffer(&FilterState, " };\n"); CopyStringToBuffer(&FilterMenu, "
\n" " \n" "
\n" "
Filter mode:
\n" "
\n"); { bool HasTopic = FALSE; bool HasMedium = FALSE; for(int i = 0; i < UniqueTopics; ++i) { if(!HasTopic) { CopyStringToBuffer(&FilterMenu, "
\n" "
Topics
\n"); HasTopic = TRUE; } CopyStringToBuffer(&FilterTopics, "
\n" " %s\n" "
\n", TopicsArray[i].Marker, TopicsArray[i].Marker, TopicsArray[i].Marker); } for(int i = 0; i < UniqueMedia; ++i) { if(!HasMedium) { CopyStringToBuffer(&FilterMedia, "
\n" "
Media
\n"); HasMedium = TRUE; } int j; for(j = 0; j < ArrayCount(CategoryMedium); ++j) { if(!StringsDiffer(MediaArray[i].Marker, CategoryMedium[j][0])) { break; } } CopyStringToBuffer(&FilterMedia, "
\n" " %s%s\n" "
\n", MediaArray[i].Marker, CategoryMedium[j][1], CategoryMedium[j][2] ); } if(HasTopic) { CopyStringToBuffer(&FilterTopics, "
\n"); CopyBuffer(&FilterMenu, &FilterTopics); } if(HasMedium) { CopyStringToBuffer(&FilterMedia, "
\n"); CopyBuffer(&FilterMenu, &FilterMedia); } } CopyStringToBuffer(&FilterMenu, "
\n" "
\n" "
\n"); } CopyBuffer(&Menus, &FilterMenu); if(HasCreditsMenu) { CopyBuffer(&Menus, &CreditsMenu); } #if CONFIG // TODO(matt): Here is where I test ParseConfig ParseConfig(&Config, HMML.metadata.annotator); #endif CopyStringToBuffer(&Menus, "
\n" " ?\n" "
\n" " ?

Keyboard Navigation

\n" "\n" "

Global Keys

\n" " W, A, P / S, D, N Jump to previous / next marker
\n"); if(HasFilterMenu) { CopyStringToBuffer(&Menus, " z Toggle filter mode V Revert filter to original state\n"); } else { CopyStringToBuffer(&Menus, " z Toggle filter mode V Revert filter to original state\n"); } CopyStringToBuffer(&Menus, "\n" "

Menu toggling

\n"); if(HasQuoteMenu) { CopyStringToBuffer(&Menus, " q Quotes\n"); } else { CopyStringToBuffer(&Menus, " q Quotes\n"); } if(HasReferenceMenu) { CopyStringToBuffer(&Menus, " r References\n"); } else { CopyStringToBuffer(&Menus, " r References\n"); } if(HasFilterMenu) { CopyStringToBuffer(&Menus, " f Filter\n"); } else { CopyStringToBuffer(&Menus, " f Filter\n"); } if(HasCreditsMenu) { CopyStringToBuffer(&Menus, " c Credits\n"); } else { CopyStringToBuffer(&Menus, " c Credits\n"); } CopyStringToBuffer(&Menus, "\n" "

Movement

\n" "
\n" "
\n" "
\n" " a\n" "
\n" "
\n" " w
\n" " s\n" "
\n" "
\n" " d\n" "
\n" "
\n" "
\n" " h\n" " j\n" " k\n" " l\n" "
\n" "
\n" "
\n" " \n" "
\n" "
\n" "
\n" " \n" "
\n" "
\n" " \n" "
\n" "
\n" "
\n" "
\n"); if(HasQuoteMenu) { CopyStringToBuffer(&Menus, "

Quotes "); if(HasReferenceMenu) { CopyStringToBuffer(&Menus, "and References Menus

\n"); } else { CopyStringToBuffer(&Menus, "and References Menus\n"); } } else { CopyStringToBuffer(&Menus, "

Quotes"); if(HasReferenceMenu) { CopyStringToBuffer(&Menus, " and References Menus

\n"); } else { CopyStringToBuffer(&Menus, " and References Menus\n"); } } if(HasQuoteMenu || HasReferenceMenu) { CopyStringToBuffer(&Menus, " Enter Jump to timecode
\n"); } else { CopyStringToBuffer(&Menus, " Enter Jump to timecode
\n"); } if(HasReferenceMenu) { CopyStringToBuffer(&Menus, "

References "); if(HasCreditsMenu) { CopyStringToBuffer(&Menus, "and Credits Menus

\n"); } else { CopyStringToBuffer(&Menus, "and Credits Menus\n"); } } else { CopyStringToBuffer(&Menus, "

References"); if(HasCreditsMenu) { CopyStringToBuffer(&Menus, " and Credits Menus

\n"); } else { CopyStringToBuffer(&Menus, " and Credits Menus\n"); } } if(HasReferenceMenu || HasCreditsMenu) { CopyStringToBuffer(&Menus, " o Open URL (in new tab)\n"); } else { CopyStringToBuffer(&Menus, " o Open URL (in new tab)\n"); } CopyStringToBuffer(&Menus, "\n"); if(HasFilterMenu) { CopyStringToBuffer(&Menus, "

Filter Menu

\n" " x, Space Toggle category and focus next
\n" " X, ShiftSpace Toggle category and focus previous
\n" " v Invert topics / media as per focus\n"); } else { CopyStringToBuffer(&Menus, "

Filter Menu

\n" " x, Space Toggle category and focus next
\n" " X, ShiftSpace Toggle category and focus previous
\n" " v Invert topics / media as per focus\n"); } if(HasCreditsMenu) { CopyStringToBuffer(&Menus, "

Credits Menu

\n" " Enter Open URL (in new tab)
\n"); } else { CopyStringToBuffer(&Menus, "

Credits Menu

\n" " Enter Open URL (in new tab)
\n"); } CopyStringToBuffer(&Menus, "
\n" "
\n" "
"); CopyStringToBuffer(&Player, "
\n" "
"); // TODO(matt): Maybe do something about indentation levels CopyStringToBuffer(&Includes, "\n" "\n" " \n" " \n" " \n" " \n" " ", HMML.metadata.project); CopyStringToBuffer(&Script, " "); #if DEBUG printf("Buffer Collation\n\n"); #endif char *CINERA_MODE = getenv("CINERA_MODE"); if(CINERA_MODE && !StringsDiffer(CINERA_MODE, "INTEGRATE")) { FILE *TemplateFile; if(!(TemplateFile = fopen("template.html", "r"))) { perror(Args[0]); hmml_free(&HMML); free(MemoryArena); return 1; } fseek(TemplateFile, 0, SEEK_END); buffer Template; Template.ID = "Template"; Template.Size = ftell(TemplateFile); fseek(TemplateFile, 0, SEEK_SET); if(!(Template.Location = malloc(Template.Size))) { perror(Args[0]); hmml_free(&HMML); free(MemoryArena); return 1; } Template.Ptr = Template.Location; fread(Template.Location, Template.Size, 1, TemplateFile); fclose(TemplateFile); buffer Output; Output.Size = Template.Size + Master.Size; Output.ID = "Output"; if(!(Output.Location = malloc(Output.Size))) { perror(Args[0]); free(Template.Location); hmml_free(&HMML); free(MemoryArena); return 1; } Output.Ptr = Output.Location; char *IncludesTag = "__CINERA_INCLUDES__"; char *TitleTag = "__CINERA_TITLE__"; char *MenusTag = "__CINERA_MENUS__"; char *PlayerTag = "__CINERA_PLAYER__"; char *ScriptTag = "__CINERA_SCRIPT__"; bool FoundIncludes = FALSE; bool FoundMenus = FALSE; bool FoundPlayer = FALSE; bool FoundScript = FALSE; while(Template.Ptr - Template.Location < Template.Size) { if(*Template.Ptr == '!' && (Template.Ptr > Template.Location && !StringsDifferT(" tag\n"); free(Template.Location); free(Output.Location); hmml_free(&HMML); free(MemoryArena); return 1; } FoundIncludes = TRUE; Output.Ptr = CommentStart; CopyBuffer(&Output, &Includes); while(Template.Ptr - Template.Location < Template.Size) { if(!StringsDifferT("-->", Template.Ptr, 0)) { Template.Ptr += StringLength("-->"); break; } ++Template.Ptr; } break; } else if(!(StringsDifferT(TitleTag, Template.Ptr, 0))) { Output.Ptr = CommentStart; CopyStringToBuffer(&Output, HMML.metadata.title); while(Template.Ptr - Template.Location < Template.Size) { if(!StringsDifferT("-->", Template.Ptr, 0)) { Template.Ptr += StringLength("-->"); break; } ++Template.Ptr; } break; } else if(!(StringsDifferT(MenusTag, Template.Ptr, 0))) { if(FoundMenus == TRUE) { fprintf(stderr, "Template contains more than one tag\n"); free(Template.Location); free(Output.Location); hmml_free(&HMML); free(MemoryArena); return 1; } FoundMenus = TRUE; Output.Ptr = CommentStart; CopyBuffer(&Output, &Menus); while(Template.Ptr - Template.Location < Template.Size) { if(!StringsDifferT("-->", Template.Ptr, 0)) { Template.Ptr += StringLength("-->"); break; } ++Template.Ptr; } break; } else if(!(StringsDifferT(PlayerTag, Template.Ptr, 0))) { if(FoundPlayer == TRUE) { fprintf(stderr, "Template contains more than one tag\n"); free(Template.Location); free(Output.Location); hmml_free(&HMML); free(MemoryArena); return 1; } FoundPlayer = TRUE; Output.Ptr = CommentStart; CopyBuffer(&Output, &Player); while(Template.Ptr - Template.Location < Template.Size) { if(!StringsDifferT("-->", Template.Ptr, 0)) { Template.Ptr += StringLength("-->"); break; } ++Template.Ptr; } break; } else if(!(StringsDifferT(ScriptTag, Template.Ptr, 0))) { if(FoundPlayer == FALSE) { fprintf(stderr, " must come after \n"); free(Template.Location); free(Output.Location); hmml_free(&HMML); free(MemoryArena); return 1; } if(FoundScript == TRUE) { fprintf(stderr, "Template contains more than one tag\n"); free(Template.Location); free(Output.Location); hmml_free(&HMML); free(MemoryArena); return 1; } FoundScript = TRUE; Output.Ptr = CommentStart; CopyBuffer(&Output, &Script); while(Template.Ptr - Template.Location < Template.Size) { if(!StringsDifferT("-->", Template.Ptr, 0)) { Template.Ptr += StringLength("-->"); break; } ++Template.Ptr; } break; } else if(!StringsDifferT("-->", Template.Ptr, 0)) { break; } *Output.Ptr++ = *Template.Ptr++; } } else { *Output.Ptr++ = *Template.Ptr++; } } if(FoundIncludes && FoundMenus && FoundPlayer && FoundScript) { FILE *OutFile; if(!(OutFile = fopen("out_integrated.html", "w"))) { perror(Args[0]); free(Template.Location); free(Output.Location); hmml_free(&HMML); free(MemoryArena); return 1; } fwrite(Output.Location, Output.Ptr - Output.Location, 1, OutFile); fclose(OutFile); } else { fprintf(stderr, "Template is missing necessary tags:\n"); if(!FoundIncludes) { fprintf(stderr, " \n"); } if(!FoundMenus) { fprintf(stderr, " \n"); } if(!FoundPlayer) { fprintf(stderr, " \n"); } if(!FoundScript) { fprintf(stderr, " \n"); } free(Template.Location); free(Output.Location); hmml_free(&HMML); free(MemoryArena); return 1; } free(Template.Location); free(Output.Location); } else { // NOTE(matt): Perform the normal collation into Master, and write-out to out.html CopyStringToBuffer(&Master, "\n" " \n"); //NOTE(matt): Here is where we do all our CopyBuffer() calls CopyBuffer(&Master, &Includes); CopyStringToBuffer(&Master, "\n" " \n" " \n"); CopyBuffer(&Master, &Menus); CopyStringToBuffer(&Master, "\n"); CopyBuffer(&Master, &Player); CopyStringToBuffer(&Master, "\n"); CopyBuffer(&Master, &Script); CopyStringToBuffer(&Master, "\n"); // CopyStringToBuffer(&Master, " \n" "\n"); FILE *OutFile; if(!(OutFile = fopen("out.html", "w"))) { perror(Args[0]); hmml_free(&HMML); free(MemoryArena); return 1; } fwrite(Master.Location, Master.Ptr - Master.Location, 1, OutFile); fclose(OutFile); } // NOTE(matt): Tree structure of "global" buffer dependencies // FilterState // Script // Annotation // Colour // Player // AnnotatorInfo // HostInfo // CreditsMenu // FilterMedia // FilterTopics // FilterMenu // ReferenceMenu // QuoteMenu // Menus // Includes // Master DeclaimBuffer(&FilterState, &ClaimedMemory); DeclaimBuffer(&Script, &ClaimedMemory); DeclaimBuffer(&Annotation, &ClaimedMemory); DeclaimBuffer(&Colour, &ClaimedMemory); DeclaimBuffer(&Player, &ClaimedMemory); DeclaimBuffer(&AnnotatorInfo, &ClaimedMemory); DeclaimBuffer(&HostInfo, &ClaimedMemory); DeclaimBuffer(&CreditsMenu, &ClaimedMemory); DeclaimBuffer(&FilterMedia, &ClaimedMemory); DeclaimBuffer(&FilterTopics, &ClaimedMemory); DeclaimBuffer(&FilterMenu, &ClaimedMemory); DeclaimBuffer(&ReferenceMenu, &ClaimedMemory); DeclaimBuffer(&QuoteMenu, &ClaimedMemory); DeclaimBuffer(&Menus, &ClaimedMemory); DeclaimBuffer(&Includes, &ClaimedMemory); DeclaimBuffer(&Master, &ClaimedMemory); } else { fprintf(stderr, "%s:%d: %s\n", Args[FileIndex], HMML.error.line, HMML.error.message); } hmml_free(&HMML); } free(MemoryArena); }