#if 0 ctime -begin ${0%.*}.ctm gcc -g -no-pie -fsanitize=address -Wall -std=c99 -pipe $0 -o ${0%.*} hmml.a -lcurl 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 // NOTE(matt): getopts //#include "config.h" // TODO(matt): Implement config.h #include #include #include #define Kilobytes(Bytes) Bytes << 10 #define Megabytes(Bytes) Bytes << 20 enum { EDITION_SINGLE, EDITION_PROJECT, EDITION_NETWORK }; 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 64 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; typedef struct { category_info Category[64]; int Count; } categories; // TODO(matt): Parse this stuff out of a config file typedef struct { char *Username; char *CreditedName; char *HomepageURL; char *SupportIcon; char *SupportURL; } credential_info; credential_info Credentials[] = { { "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"}, { "/y_lee", "Yunsup Lee", "https://www.linkedin.com/in/yunsup-lee-385b692b/", "", ""}, { "/a_waterman", "Andrew Waterman", "https://www.linkedin.com/in/andrew-waterman-76805788", "", ""}, { "debiatan", "Miguel Lechón", "http://blog.debiatan.net/", "", ""}, }; typedef struct { char *Medium; char *Icon; char *WrittenName; } category_medium; category_medium CategoryMedium[] = { // medium icon written name { "afk", "…" , "Away from Keyboard"}, // TODO(matt): Filter this out by default { "authored", "🗪", "Chat Comment"}, // TODO(matt): Conditionally handle Chat vs Guest Comments { "blackboard", "🖌", "Blackboard"}, { "experience", "🍷", "Experience"}, { "owl", "🦉", "Owl of Shame"}, { "programming", "🖮", "Programming"}, // TODO(matt): Potentially make this configurable per project { "rant", "💢", "Rant"}, { "research", "📖", "Research"}, { "run", "🏃", "In-Game"}, // TODO(matt): Potentially make this configurable per project { "trivia", "🎲", "Trivia"}, }; #define EDITION EDITION_SINGLE #define ArrayCount(A) sizeof(A)/sizeof(*(A)) #define ClaimBuffer(MemoryArena, ClaimedMemory, Buffer, ID, Size) if(__ClaimBuffer(MemoryArena, ClaimedMemory, Buffer, ID, Size))\ {\ fprintf(stderr, "%s:%d: MemoryArena cannot contain %s of size %d\n", __FILE__, __LINE__, ID, Size);\ hmml_free(&HMML);\ FreeBuffer(MemoryArena);\ return 1;\ }; void FreeBuffer(buffer *Buffer) { free(Buffer->Location); } int __ClaimBuffer(buffer *MemoryArena, int *ClaimedMemory, buffer *Buffer, char *ID, int Size) { if(*ClaimedMemory + Size > MemoryArena->Size) { return 1; } Buffer->Location = MemoryArena->Location + *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 return 0; } 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++; } *Dest->Ptr = '\0'; } __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 CopyStringNoFormatT(char *Dest, char *String, char Terminator) { int Length = 0; while(*String != Terminator) { *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; } enum { CreditsError_NoHost, CreditsError_NoAnnotator, CreditsError_NoCredentials }; int SearchCredentials(buffer *CreditsMenu, bool *HasCreditsMenu, char *ImagesDir, char *Person, char* Role) { bool Found = FALSE; for(int CredentialIndex = 0; CredentialIndex < ArrayCount(Credentials); ++CredentialIndex) { if(!StringsDiffer(Person, Credentials[CredentialIndex].Username)) { Found = TRUE; if(*HasCreditsMenu == FALSE) { CopyStringToBuffer(CreditsMenu, "
\n" "
\n" " Credits\n" "
\n"); *HasCreditsMenu = TRUE; } CopyStringToBuffer(CreditsMenu, " \n"); if(*Credentials[CredentialIndex].HomepageURL) { CopyStringToBuffer(CreditsMenu, " \n" "
%s
\n" "
%s
\n" "
\n", Credentials[CredentialIndex].HomepageURL, Role, Credentials[CredentialIndex].CreditedName); } else { CopyStringToBuffer(CreditsMenu, "
\n" "
%s
\n" "
%s
\n" "
\n", Role, Credentials[CredentialIndex].CreditedName); } if(*Credentials[CredentialIndex].SupportIcon && *Credentials[CredentialIndex].SupportURL) { CopyStringToBuffer(CreditsMenu, " \n", Credentials[CredentialIndex].SupportURL, ImagesDir, Credentials[CredentialIndex].SupportIcon); } CopyStringToBuffer(CreditsMenu, "
\n"); } } return Found ? 0 : CreditsError_NoCredentials; } int BuildCredits(buffer *CreditsMenu, bool *HasCreditsMenu, char *ImagesDir, HMML_VideoMetaData Metadata) // TODO(matt): Make this take the Credentials, once we are parsing them from a config { if(Metadata.member) { if(SearchCredentials(CreditsMenu, HasCreditsMenu, ImagesDir, Metadata.member, "Host")) { printf("No credentials for %s. Please contact matt@handmadedev.org with their:\n" " Full name\n" " Homepage URL (optional)\n" " Financial support info, e.g. Patreon URL (optional)\n", Metadata.member); } } else { if(*HasCreditsMenu == TRUE) { CopyStringToBuffer(CreditsMenu, "
\n" "
\n"); } return CreditsError_NoHost; } if(Metadata.co_host_count > 0) { for(int i = 0; i < Metadata.co_host_count; ++i) { if(SearchCredentials(CreditsMenu, HasCreditsMenu, ImagesDir, Metadata.co_hosts[i], "Co-host")) { printf("No credentials for %s. Please contact matt@handmadedev.org with their:\n" " Full name\n" " Homepage URL (optional)\n" " Financial support info, e.g. Patreon URL (optional)\n", Metadata.co_hosts[i]); } } } if(Metadata.guest_count > 0) { for(int i = 0; i < Metadata.guest_count; ++i) { if(SearchCredentials(CreditsMenu, HasCreditsMenu, ImagesDir, Metadata.guests[i], "Guest")) { printf("No credentials for %s. Please contact matt@handmadedev.org with their:\n" " Full name\n" " Homepage URL (optional)\n" " Financial support info, e.g. Patreon URL (optional)\n", Metadata.guests[i]); } } } if(Metadata.annotator_count > 0) { for(int i = 0; i < Metadata.annotator_count; ++i) { if(SearchCredentials(CreditsMenu, HasCreditsMenu, ImagesDir, Metadata.annotators[i], "Annotator")) { printf("No credentials for %s. Please contact matt@handmadedev.org with their:\n" " Full name\n" " Homepage URL (optional)\n" " Financial support info, e.g. Patreon URL (optional)\n", Metadata.annotators[i]); } } } else { if(*HasCreditsMenu == TRUE) { CopyStringToBuffer(CreditsMenu, " \n" " \n"); } return CreditsError_NoAnnotator; } if(*HasCreditsMenu == TRUE) { CopyStringToBuffer(CreditsMenu, " \n" " \n"); } return 0; } int BuildReference(ref_info *ReferencesArray, int RefIdentifier, int UniqueRefs, HMML_Reference Ref, HMML_Annotation Anno) { #define REF_SITE (1 << 0) #define REF_PAGE (1 << 1) #define REF_URL (1 << 2) #define REF_TITLE (1 << 3) #define REF_ARTICLE (1 << 4) #define REF_AUTHOR (1 << 5) #define REF_EDITOR (1 << 6) #define REF_PUBLISHER (1 << 7) #define REF_ISBN (1 << 8) int Mask = 0; if(Ref.site) { Mask |= REF_SITE; } if(Ref.page) { Mask |= REF_PAGE; } if(Ref.url) { Mask |= REF_URL; } if(Ref.title) { Mask |= REF_TITLE; } if(Ref.article) { Mask |= REF_ARTICLE; } if(Ref.author) { Mask |= REF_AUTHOR; } if(Ref.editor) { Mask |= REF_EDITOR; } if(Ref.publisher) { Mask |= REF_PUBLISHER; } if(Ref.isbn) { Mask |= REF_ISBN; } if((REF_URL | REF_TITLE | REF_AUTHOR | REF_PUBLISHER | REF_ISBN) == Mask) { 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_AUTHOR | REF_SITE | REF_PAGE | REF_URL) == Mask) { CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url); CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, Ref.site); CopyString(ReferencesArray[UniqueRefs].RefTitle, "%s: \"%s\"", Ref.author, Ref.page); CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url); } else if((REF_PAGE | REF_URL | REF_TITLE) == Mask) { 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) == Mask) { 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) == Mask) { 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) == Mask) { 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) == Mask) { 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) == Mask) { 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) == Mask) { CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url); CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title); CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url); } else if((REF_SITE | REF_URL) == Mask) { 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; } void InsertCategory(categories *Topics, categories *Media, bool *HasTopic, bool *HasMedium, char *Marker) { bool IsMedium = FALSE; int CategoryMediumIndex; for(CategoryMediumIndex = 0; CategoryMediumIndex < ArrayCount(CategoryMedium); ++CategoryMediumIndex) { if(!StringsDiffer(CategoryMedium[CategoryMediumIndex].Medium, Marker)) { IsMedium = TRUE; *HasMedium = TRUE; break; } } int CategoryIndex; if(IsMedium) { for(CategoryIndex = 0; CategoryIndex < Media->Count; ++CategoryIndex) { if(!StringsDiffer(CategoryMedium[CategoryMediumIndex].Medium, Media->Category[CategoryIndex].Marker)) { return; } if((StringsDiffer(CategoryMedium[CategoryMediumIndex].WrittenName, Media->Category[CategoryIndex].WrittenText)) < 0) { int CategoryCount; for(CategoryCount = Media->Count; CategoryCount > CategoryIndex; --CategoryCount) { CopyString(Media->Category[CategoryCount].Marker, Media->Category[CategoryCount-1].Marker); CopyString(Media->Category[CategoryCount].WrittenText, Media->Category[CategoryCount-1].WrittenText); } CopyString(Media->Category[CategoryCount].Marker, CategoryMedium[CategoryMediumIndex].Medium); CopyString(Media->Category[CategoryCount].WrittenText, CategoryMedium[CategoryMediumIndex].WrittenName); break; } } if(CategoryIndex == Media->Count) { CopyString(Media->Category[CategoryIndex].Marker, CategoryMedium[CategoryMediumIndex].Medium); CopyString(Media->Category[CategoryIndex].WrittenText, CategoryMedium[CategoryMediumIndex].WrittenName); } ++Media->Count; } else { *HasTopic = TRUE; for(CategoryIndex = 0; CategoryIndex < Topics->Count; ++CategoryIndex) { if(!StringsDiffer(Marker, Topics->Category[CategoryIndex].Marker)) { return; } if((StringsDiffer(Marker, Topics->Category[CategoryIndex].Marker)) < 0) { int CategoryCount; for(CategoryCount = Topics->Count; CategoryCount > CategoryIndex; --CategoryCount) { CopyString(Topics->Category[CategoryCount].Marker, Topics->Category[CategoryCount-1].Marker); } CopyString(Topics->Category[CategoryCount].Marker, Marker); break; } } if(CategoryIndex == Topics->Count) { CopyString(Topics->Category[CategoryIndex].Marker, Marker); } ++Topics->Count; } return; } void BuildCategories(buffer *AnnotationClass, buffer *TopicDots, categories LocalTopics, categories LocalMedia, int *MarkerIndex) { if(LocalTopics.Count > 0) { CopyStringToBuffer(TopicDots, ""); for(int i = 0; i < LocalTopics.Count; ++i) { CopyStringToBuffer(TopicDots, "
", SanitisePunctuation(LocalTopics.Category[i].Marker), SanitisePunctuation(LocalTopics.Category[i].Marker)); CopyStringToBuffer(AnnotationClass, " cat_%s", SanitisePunctuation(LocalTopics.Category[i].Marker)); } CopyStringToBuffer(TopicDots, "
"); } for(int i = 0; i < LocalMedia.Count; ++i) { CopyStringToBuffer(AnnotationClass, " %s", SanitisePunctuation(LocalMedia.Category[i].Marker)); } CopyStringToBuffer(AnnotationClass, "\""); } int StringToInt(char *String) { int Result = 0; while(*String) { Result = Result * 10 + (*String - '0'); ++String; } return Result; } int MakeDir(char *Path) { int i = StringLength(Path); int Ancestors = 0; while(mkdir(Path, 00755) == -1) { while(Path[i] != '/' && i > 0) { --i; } ++Ancestors; Path[i] = '\0'; if(i == 0) { return 1; } } while(Ancestors > 0) { while(Path[i] != '\0') { ++i; } Path[i] = '/'; --Ancestors; if((mkdir(Path, 00755)) == -1) { return 1; } } return 0; } size_t CurlIntoBuffer(char *InPtr, size_t CharLength, size_t Chars, char **OutputPtr) { int Length = CharLength * Chars; int i; for(i = 0; InPtr[i] && i < Length; ++i) { *((*OutputPtr)++) = InPtr[i]; } **OutputPtr = '\0'; return Length; }; void CurlQuotes(buffer *QuoteStaging, char *QuotesURL) { CURL *curl = curl_easy_init(); if(curl) { CURLcode res; curl_easy_setopt(curl, CURLOPT_WRITEDATA, &QuoteStaging->Ptr); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlIntoBuffer); curl_easy_setopt(curl, CURLOPT_URL, QuotesURL); if((res = curl_easy_perform(curl))) { fprintf(stderr, "%s", curl_easy_strerror(res)); } curl_easy_cleanup(curl); } } int SearchQuotes(buffer QuoteStaging, int CacheSize, quote_info *Info, int ID) { QuoteStaging.Ptr = QuoteStaging.Location; while(QuoteStaging.Ptr - QuoteStaging.Location < CacheSize) { char InID[4] = { 0 }; char InTime[16] = { 0 }; char *OutPtr = InID; QuoteStaging.Ptr += CopyStringNoFormatT(OutPtr, QuoteStaging.Ptr, ','); if(StringToInt(InID) == ID) { QuoteStaging.Ptr += 1; OutPtr = InTime; QuoteStaging.Ptr += CopyStringNoFormatT(OutPtr, QuoteStaging.Ptr, ','); long int Time = StringToInt(InTime); char DayString[3]; strftime(DayString, 3, "%d", gmtime(&Time)); int Day = StringToInt(DayString); char DaySuffix[3]; if(DayString[1] == '1' && Day != 11) { CopyString(DaySuffix, "st"); } else if(DayString[1] == '2' && Day != 12) { CopyString(DaySuffix, "nd"); } else if(DayString[1] == '3' && Day != 13) { CopyString(DaySuffix, "rd"); } else { CopyString(DaySuffix, "th"); } char MonthYear[32]; strftime(MonthYear, 32, "%B, %Y", gmtime(&Time)); CopyString(Info->Date, "%d%s %s", Day, DaySuffix, MonthYear); QuoteStaging.Ptr += 1; OutPtr = Info->Text; QuoteStaging.Ptr += CopyStringNoFormatT(OutPtr, QuoteStaging.Ptr, '\n'); FreeBuffer(&QuoteStaging); return 0; } else { while(*QuoteStaging.Ptr != '\n') { ++QuoteStaging.Ptr; } ++QuoteStaging.Ptr; } } return 1; } int BuildQuote(quote_info *Info, char *Speaker, int ID, char *CacheDir) { // TODO(matt): Rebuild cache option char QuoteCacheDir[255]; CopyString(QuoteCacheDir, "%s/quotes", CacheDir); char QuoteCachePath[255]; CopyString(QuoteCachePath, "%s/%s", QuoteCacheDir, Speaker); FILE *QuoteCache; char QuotesURL[256]; CopyString(QuotesURL, "https://dev.abaines.me.uk/quotes/%s.raw", Speaker); bool CacheAvailable = FALSE; if(!(QuoteCache = fopen(QuoteCachePath, "a+"))) { if(MakeDir(QuoteCacheDir) == 0) { CacheAvailable = TRUE; }; if(!(QuoteCache = fopen(QuoteCachePath, "a+"))) { perror(QuoteCachePath); } else { CacheAvailable = TRUE; } } else { CacheAvailable = TRUE; } buffer QuoteStaging; QuoteStaging.ID = "QuoteStaging"; QuoteStaging.Size = Kilobytes(256); if(!(QuoteStaging.Location = malloc(QuoteStaging.Size))) { perror("BuildQuote"); } QuoteStaging.Ptr = QuoteStaging.Location; if(CacheAvailable) { fseek(QuoteCache, 0, SEEK_END); int FileSize = ftell(QuoteCache); fseek(QuoteCache, 0, SEEK_SET); fread(QuoteStaging.Location, FileSize, 1, QuoteCache); fclose(QuoteCache); if(SearchQuotes(QuoteStaging, FileSize, Info, ID) == 1) { CurlQuotes(&QuoteStaging, QuotesURL); if(!(QuoteCache = fopen(QuoteCachePath, "w"))) { perror(QuoteCachePath); } fwrite(QuoteStaging.Location, QuoteStaging.Ptr - QuoteStaging.Location, 1, QuoteCache); fclose(QuoteCache); int CacheSize = QuoteStaging.Ptr - QuoteStaging.Location; QuoteStaging.Ptr = QuoteStaging.Location; if(SearchQuotes(QuoteStaging, CacheSize, Info, ID) == 1) { FreeBuffer(&QuoteStaging); return 1; } } } else { CurlQuotes(&QuoteStaging, QuotesURL); int CacheSize = QuoteStaging.Ptr - QuoteStaging.Location; QuoteStaging.Ptr = QuoteStaging.Location; if(SearchQuotes(QuoteStaging, CacheSize, Info, ID) == 1) { FreeBuffer(&QuoteStaging); return 1; } } return 0; } void GenerateTopicColours(buffer *Colour, char *Topic, char *TopicsDir) { for(int i = 0; i < ArrayCount(CategoryMedium); ++i) { if(!StringsDiffer(Topic, CategoryMedium[i].Medium)) { return; } } FILE *TopicsFile; char *TopicsBuffer; // TODO(matt): Consider (optionally) pulling this path from the config char TopicsPath[255]; CopyString(TopicsPath, "%s/cinera_topcs.css", TopicsDir); 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 void PrintUsage(char *BinaryLocation, char *DefaultCSSDir, char *DefaultImagesDir, char *DefaultJSDir, char *DefaultDefaultMedium, char *DefaultOutLocation, char *DefaultTemplateLocation) { fprintf(stderr, "Usage: %s [option(s)] filename(s)\n" "\n" "Options:\n" " -c \n" " Override default CSS directory (\"%s\")\n" " -f\n" " Force integration with an incomplete template\n" " -i \n" " Override default images directory (\"%s\")\n" " -j \n" " Override default JS directory (\"%s\")\n" " -m \n" " Override default default medium (\"%s\")\n" " -o \n" " Override default output location (\"%s\")\n" " -t