From f9bebe29241f5f61264c468a556af4580f10cbea Mon Sep 17 00:00:00 2001 From: Matt Mascarenhas Date: Thu, 7 Sep 2017 22:41:08 +0100 Subject: [PATCH] hmml_to_html.c: Generate full project [#25] Full project currently includes a "Table of Contents" linking to pages for each set of annotations. It should successfully generate pages for all well-formed annotations, even if >= 1 are not well-formed. --- hmml_to_html/hmml_to_html.c | 1410 +++++++++++++++++++++-------------- 1 file changed, 859 insertions(+), 551 deletions(-) diff --git a/hmml_to_html/hmml_to_html.c b/hmml_to_html/hmml_to_html.c index 9d66036..003349a 100644 --- a/hmml_to_html/hmml_to_html.c +++ b/hmml_to_html/hmml_to_html.c @@ -23,6 +23,9 @@ typedef unsigned int bool; #include #include #include +#include // NOTE(matt): strerror +#include //NOTE(matt): errno + #define Kilobytes(Bytes) Bytes << 10 #define Megabytes(Bytes) Bytes << 20 @@ -34,12 +37,47 @@ enum EDITION_NETWORK } editions; +enum +{ + // NOTE(matt): https://tools.ietf.org/html/rfc5424#section-6.2.1 + LOG_EMERGENCY, + LOG_ALERT, + LOG_CRITICAL, + LOG_ERROR, + LOG_WARNING, + LOG_NOTICE, + LOG_INFORMATIONAL, + LOG_DEBUG +} log_levels; + enum { MODE_BARE, MODE_INTEGRATE } modes; +enum +{ + RC_ARENA_FULL, + RC_ERROR_DIRECTORY, + RC_ERROR_FATAL, + RC_ERROR_FILE, + RC_ERROR_HMML, + RC_ERROR_MAX_REFS, + RC_ERROR_MEMORY, + RC_ERROR_QUOTE, + RC_ERROR_TEMPLATE, + RC_FAILURE, + RC_FOUND, + RC_UNFOUND, + RC_INVALID_TEMPLATE, + RC_INVALID_REFERENCE, + RC_NOOP, + RC_REFRESHED, + RC_RIP, + RC_SUCCESS +} returns; + typedef struct { char *BaseDir; @@ -48,15 +86,25 @@ typedef struct int Edition; char *ImagesDir; char *JSDir; + int LogLevel; char *DefaultMedium; int Mode; char *OutLocation; char *OutIntegratedLocation; bool ForceIntegration; char *ProjectDir; - char *TemplateLocation; + char *TemplateIndexLocation; + char *TemplatePlayerLocation; } config; +typedef struct +{ + void *Location; + void *Ptr; + char *ID; + int Size; +} arena; + typedef struct { char *Location; @@ -65,6 +113,47 @@ typedef struct int Size; } buffer; +enum +{ + TAG_INDEX, + + TAG_INCLUDES, + TAG_MENUS, + TAG_PLAYER, + TAG_SCRIPT, + + TAG_TITLE +} template_tags; + +typedef struct +{ + int Code; // template_tags + char *Tag; +} tag; + +tag Tags[] = { + { TAG_INDEX, "__CINERA_INDEX__" }, + { TAG_INCLUDES, "__CINERA_INCLUDES__" }, + { TAG_MENUS, "__CINERA_MENUS__" }, + { TAG_PLAYER, "__CINERA_PLAYER__" }, + { TAG_SCRIPT, "__CINERA_SCRIPT__" }, + { TAG_TITLE, "__CINERA_TITLE__" }, +}; + +typedef struct +{ + int Offset; + int TagCode; +} tag_offset; + +typedef struct +{ + char Filename[120]; + tag_offset Tag[16]; + int Validity; // NOTE(matt): Bitmask describing which page the template is valid for, i.e. contents and / or player page + int TagCount; +} template; + typedef struct { buffer IncludesIndex; @@ -74,6 +163,7 @@ typedef struct buffer Player; buffer Script; char Title[255]; + char Project[32]; } buffers; // TODO(matt): Consider putting the ref_info and quote_info into linked lists on the heap, just to avoid all the hardcoded sizes @@ -324,17 +414,22 @@ StringsDifferT(char *A, // NOTE(matt): Null-terminated string int MakeDir(char *Path) { + // TODO(matt): Correctly check for permissions int i = StringLength(Path); int Ancestors = 0; while(mkdir(Path, 00755) == -1) { + if(errno == EACCES) + { + return RC_ERROR_DIRECTORY; + } while(Path[i] != '/' && i > 0) { --i; } ++Ancestors; Path[i] = '\0'; - if(i == 0) { return 1; } + if(i == 0) { return RC_ERROR_DIRECTORY; } } while(Ancestors > 0) { @@ -346,46 +441,10 @@ MakeDir(char *Path) --Ancestors; if((mkdir(Path, 00755)) == -1) { - return 1; + return RC_ERROR_DIRECTORY; } } - return 0; -} - -void -FreeBuffer(buffer *Buffer) -{ - free(Buffer->Location); -} - -#if 0 -#define ClaimBuffer(MemoryArena, Buffer, ID, Size) if(__ClaimBuffer(MemoryArena, 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;\ -}; -#endif - -int -ClaimBuffer(buffer *MemoryArena, buffer *Buffer, char *ID, int Size) -{ - if(MemoryArena->Ptr - MemoryArena->Location + Size > MemoryArena->Size) - { - return 1; - } - Buffer->Location = MemoryArena->Ptr; - Buffer->Size = Size; - Buffer->ID = ID; - MemoryArena->Ptr += Buffer->Size; - *Buffer->Location = '\0'; - Buffer->Ptr = Buffer->Location; -#if DEBUG - printf(" Claimed: %s: %d\n" - " Total ClaimedMemory: %ld\n\n", Buffer->ID, Buffer->Size, MemoryArena->Ptr - MemoryArena->Location); -#endif - return 0; + return RC_SUCCESS; } void @@ -411,10 +470,75 @@ LogUsage(buffer Buffer, char *CacheDir) fclose(LogFile); } +__attribute__ ((format (printf, 3, 4))) +void +LogError(config Config, int LogLevel, char *Format, ...) +{ + if(Config.LogLevel >= LogLevel) + { + char LogPath[255]; + CopyString(LogPath, "%s/%s", Config.CacheDir, "errors.log"); + FILE *LogFile; + if(!(LogFile = fopen(LogPath, "a+"))) + { + MakeDir(Config.CacheDir); + if(!(LogFile = fopen(LogPath, "a+"))) + { + perror("LogUsage"); + return; + } + } + + va_list Args; + va_start(Args, Format); + vfprintf(LogFile, Format, Args); + va_end(Args); + // TODO(matt): Include the LogLevel "string" and the current wall time + fprintf(LogFile, "\n"); + fclose(LogFile); + } +} + +void +FreeBuffer(buffer *Buffer) +{ + free(Buffer->Location); +} + +#if 0 +#define ClaimBuffer(MemoryArena, Buffer, ID, Size) if(__ClaimBuffer(MemoryArena, 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;\ +}; +#endif + +int +ClaimBuffer(arena *MemoryArena, buffer *Buffer, char *ID, int Size) +{ + if(MemoryArena->Ptr - MemoryArena->Location + Size > MemoryArena->Size) + { + return RC_ARENA_FULL; + } + Buffer->Location = (char *)MemoryArena->Ptr; + Buffer->Size = Size; + Buffer->ID = ID; + MemoryArena->Ptr += Buffer->Size; + *Buffer->Location = '\0'; + Buffer->Ptr = Buffer->Location; +#if DEBUG + printf(" Claimed: %s: %d\n" + " Total ClaimedMemory: %ld\n\n", Buffer->ID, Buffer->Size, MemoryArena->Ptr - MemoryArena->Location); +#endif + return RC_SUCCESS; +} + #define DeclaimBuffer(MemoryArena, Buffer) __DeclaimBuffer(MemoryArena, Buffer, Config) void -__DeclaimBuffer(buffer *MemoryArena, buffer *Buffer, config Config) +__DeclaimBuffer(arena *MemoryArena, buffer *Buffer, config Config) { *Buffer->Location = '\0'; MemoryArena->Ptr -= Buffer->Size; @@ -434,10 +558,41 @@ __DeclaimBuffer(buffer *MemoryArena, buffer *Buffer, config Config) if(PercentageUsed >= 80.0f) { // TODO(matt): Implement either dynamically growing buffers, or phoning home to matt@handmadedev.org + LogError(Config, LOG_ERROR, "%s used %.2f%% of its allotted memory\n", Buffer->ID, PercentageUsed); fprintf(stderr, "Warning: %s used %.2f%% of its allotted memory\n", Buffer->ID, PercentageUsed); } } +int +ClaimTemplate(arena *MemoryArena, template **Template, char *ID) +{ + if(MemoryArena->Ptr - MemoryArena->Location + sizeof(template) > MemoryArena->Size) + { + return RC_ARENA_FULL; + } + *Template = (template *)MemoryArena->Ptr; + CopyString((*Template)->Filename, ID); + MemoryArena->Ptr += sizeof(template); +#if DEBUG + printf(" Claimed: %s metadata: %ld\n" + " Total ClaimedMemory: %ld\n\n", (*Template)->Filename, sizeof(template), MemoryArena->Ptr - MemoryArena->Location); +#endif + return RC_SUCCESS; +} + +int +DeclaimTemplate(arena *MemoryArena, template **Template) +{ + MemoryArena->Ptr -= sizeof(template); +#if DEBUG + printf("Declaimed: %s metadata\n" + " Total ClaimedMemory: %ld\n\n", + (*Template)->Filename, + MemoryArena->Ptr - MemoryArena->Location); +#endif + return RC_SUCCESS; +} + int TimecodeToSeconds(char *Timecode) { @@ -776,7 +931,6 @@ BuildReference(ref_info *ReferencesArray, int RefIdentifier, int UniqueRefs, HMM 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) @@ -998,7 +1152,7 @@ SearchQuotes(buffer QuoteStaging, int CacheSize, quote_info *Info, int ID) QuoteStaging.Ptr += CopyStringNoFormatT(OutPtr, QuoteStaging.Ptr, '\n'); FreeBuffer(&QuoteStaging); - return 0; + return RC_FOUND; } else { @@ -1009,7 +1163,7 @@ SearchQuotes(buffer QuoteStaging, int CacheSize, quote_info *Info, int ID) ++QuoteStaging.Ptr; } } - return 1; + return RC_UNFOUND; } int @@ -1029,13 +1183,13 @@ BuildQuote(quote_info *Info, char *Speaker, int ID, char *CacheDir) if(!(QuoteCache = fopen(QuoteCachePath, "a+"))) { - if(MakeDir(QuoteCacheDir) == 0) + if(MakeDir(QuoteCacheDir) == RC_SUCCESS) { CacheAvailable = TRUE; }; if(!(QuoteCache = fopen(QuoteCachePath, "a+"))) { - perror(QuoteCachePath); + fprintf(stderr, "Unable to open quote cache %s: %s\n", QuoteCachePath, strerror(errno)); } else { @@ -1052,7 +1206,8 @@ BuildQuote(quote_info *Info, char *Speaker, int ID, char *CacheDir) QuoteStaging.Size = Kilobytes(256); if(!(QuoteStaging.Location = malloc(QuoteStaging.Size))) { - perror("BuildQuote"); + fclose(QuoteCache); + return RC_ERROR_MEMORY; } QuoteStaging.Ptr = QuoteStaging.Location; @@ -1065,7 +1220,7 @@ BuildQuote(quote_info *Info, char *Speaker, int ID, char *CacheDir) fread(QuoteStaging.Location, FileSize, 1, QuoteCache); fclose(QuoteCache); - if(SearchQuotes(QuoteStaging, FileSize, Info, ID) == 1) + if(SearchQuotes(QuoteStaging, FileSize, Info, ID) == RC_UNFOUND) { CurlQuotes(&QuoteStaging, QuotesURL); @@ -1100,14 +1255,14 @@ BuildQuote(quote_info *Info, char *Speaker, int ID, char *CacheDir) return 0; } -void +int GenerateTopicColours(char *Topic, char *TopicsDir) { for(int i = 0; i < ArrayCount(CategoryMedium); ++i) { if(!StringsDiffer(Topic, CategoryMedium[i].Medium)) { - return; + return RC_NOOP; } } @@ -1122,11 +1277,9 @@ GenerateTopicColours(char *Topic, char *TopicsDir) 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; + return RC_ERROR_MEMORY; } fread(TopicsBuffer, TopicsLength, 1, TopicsFile); @@ -1140,7 +1293,7 @@ GenerateTopicColours(char *Topic, char *TopicsDir) { free(TopicsBuffer); fclose(TopicsFile); - return; + return RC_NOOP; } while(TopicsPtr - TopicsBuffer < TopicsLength && *TopicsPtr != '\n') { @@ -1156,11 +1309,11 @@ GenerateTopicColours(char *Topic, char *TopicsDir) fclose(TopicsFile); free(TopicsBuffer); + return RC_SUCCESS; } else { - perror("GenerateTopicColours"); - return; + return RC_ERROR_FILE; } } @@ -1180,14 +1333,19 @@ PrintUsage(char *BinaryLocation, config DefaultConfig) " Override default images directory (\"%s\")\n" " -j \n" " Override default JS directory (\"%s\")\n" + " -l \n" + " Override default log level (%d), where n is from 0 (terse) to 7 (verbose)\n" " -m \n" " Override default default medium (\"%s\")\n" " -o \n" " Override default output location (\"%s\")\n" " -p \n" " Override default project directory (\"%s\")\n" - " -t