From c1d6c877465fa5c6585cc00ccb8ca17b55bb849f Mon Sep 17 00:00:00 2001 From: Matt Mascarenhas Date: Thu, 4 Jan 2018 20:55:51 +0000 Subject: [PATCH] cinera.c: Bespoke template in Single Edition Also fail gracefully if supplied with an .hmml file containing an incomplete [video] node, or a template containing tags whose buffers cannot be populated --- cinera/cinera.c | 263 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 189 insertions(+), 74 deletions(-) diff --git a/cinera/cinera.c b/cinera/cinera.c index ecfcb6b..92bda01 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -14,7 +14,7 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, .Minor = 5, - .Patch = 17 + .Patch = 18 }; // TODO(matt): Copy in the DB 3 stuff from cinera_working.c @@ -225,6 +225,8 @@ typedef struct char *PlayerLocation; // Relative to Base{Dir,URL} char *PlayerURLPrefix; /* NOTE(matt): This will become a full blown customisable output URL. For now it simply replaces the ProjectID */ + // Single Edition - Input + char SingleHMMLFilePath[256]; // Single Edition - Output char *OutLocation; @@ -758,10 +760,48 @@ enum TEMPLATE_BESPOKE } templates; +char * +GetDirectoryPath(char *Filepath) +{ + char *Ptr = Filepath + StringLength(Filepath) - 1; + while(Ptr > Filepath && *Ptr != '/') + { + --Ptr; + } + if(Ptr == Filepath) + { + *Ptr++ = '.'; + } + *Ptr = '\0'; + return Filepath; +} + +char * +GetBaseFilename(char *Filepath, + char *Extension // Including the "." + // Pass 0 to retain the whole file path, only without its parent directories + ) +{ + char *BaseFilename = Filepath + StringLength(Filepath) - 1; + while(BaseFilename > Filepath && *BaseFilename != '/') + { + --BaseFilename; + } + if(*BaseFilename == '/') + { + ++BaseFilename; + } + BaseFilename[StringLength(BaseFilename) - StringLength(Extension)] = '\0'; + return BaseFilename; +} + void ConstructTemplatePath(template *Template, int TemplateType) { - // TODO(matt): Consider just straight up having the TemplateDir relative to the ProjectDir... + // NOTE(matt): Bespoke template paths are set relative to: + // in Project Edition: ProjectDir + // in Single Edition: Parent directory of .hmml file + if(Template->Metadata.Filename[0] != '/') { char Temp[256]; @@ -769,7 +809,14 @@ ConstructTemplatePath(template *Template, int TemplateType) char *Ptr = Template->Metadata.Filename; if(TemplateType == TEMPLATE_BESPOKE) { - Ptr += CopyString(Ptr, "%s/", Config.ProjectDir); + if(Config.Edition == EDITION_SINGLE) + { + Ptr += CopyString(Ptr, "%s/", GetDirectoryPath(Config.SingleHMMLFilePath)); + } + else + { + Ptr += CopyString(Ptr, "%s/", Config.ProjectDir); + } } else { @@ -1633,10 +1680,10 @@ BuildQuote(quote_info *Info, char *Speaker, int ID) int CacheSize = QuoteStaging.Ptr - QuoteStaging.Location; QuoteStaging.Ptr = QuoteStaging.Location; - if(SearchQuotes(&QuoteStaging, CacheSize, Info, ID) == 1) + if(SearchQuotes(&QuoteStaging, CacheSize, Info, ID) == RC_UNFOUND) { FreeBuffer(&QuoteStaging); - return 1; + return RC_UNFOUND; } } } @@ -1645,14 +1692,14 @@ BuildQuote(quote_info *Info, char *Speaker, int ID) CurlQuotes(&QuoteStaging, QuotesURL); int CacheSize = QuoteStaging.Ptr - QuoteStaging.Location; QuoteStaging.Ptr = QuoteStaging.Location; - if(SearchQuotes(&QuoteStaging, CacheSize, Info, ID) == 1) + if(SearchQuotes(&QuoteStaging, CacheSize, Info, ID) == RC_UNFOUND) { FreeBuffer(&QuoteStaging); - return 1; + return RC_UNFOUND; } } - return 0; + return RC_SUCCESS; } int @@ -2176,6 +2223,8 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen RewindBuffer(&CollationBuffers->ScriptPlayer); RewindBuffer(&CollationBuffers->IncludesIndex); RewindBuffer(&CollationBuffers->ScriptIndex); + *CollationBuffers->Title = '\0'; + *CollationBuffers->ProjectName = '\0'; char Filepath[256]; if(Config.Edition == EDITION_PROJECT) @@ -2200,9 +2249,58 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen if(HMML.well_formed) { + char *BaseFilename = GetBaseFilename(Filename, ".hmml"); + if(!HMML.metadata.title) + { + fprintf(stderr, "Please set the title attribute in the [video] node of your .hmml file\n"); + fprintf(stderr, "\e[1;31mSkipping\e[0m %s\n", BaseFilename); + hmml_free(&HMML); + return RC_ERROR_HMML; + } + CopyString(CollationBuffers->Title, HMML.metadata.title); + + if(!HMML.metadata.member) + { + fprintf(stderr, "Please set the member attribute in the [video] node of your .hmml file\n"); + fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title); + hmml_free(&HMML); + return RC_ERROR_HMML; + } + + if(!HMML.metadata.project && !StringsDiffer(Config.Theme, "")) + { + fprintf(stderr, "Unable to determine which theme to apply to the HTML\n" + "Please set at least one of:\n" + "\t1. project attribute in the [video] node of your .hmml file\n" + "\t2. ProjectID on the command line with -p\n" + "\t3. Style on the command line with -s\n" + ); + fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title); + hmml_free(&HMML); + return RC_ERROR_HMML; + } + + if(!HMML.metadata.id) + { + fprintf(stderr, "Please set the id attribute in the [video] node of your .hmml file\n"); + fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title); + hmml_free(&HMML); + return RC_ERROR_HMML; + } + CopyString(CollationBuffers->VideoID, HMML.metadata.id); + + buffer URLPlayer; + ClaimBuffer(&URLPlayer, "URLPlayer", 2048); + ConstructPlayerURL(&URLPlayer, BaseFilename); + CopyString(CollationBuffers->URLPlayer, URLPlayer.Location); + DeclaimBuffer(&URLPlayer); + for(int ProjectIndex = 0; ProjectIndex < ArrayCount(ProjectInfo); ++ProjectIndex) { - if(!StringsDiffer(ProjectInfo[ProjectIndex].ProjectID, Config.ProjectID)) + if(!StringsDiffer(ProjectInfo[ProjectIndex].ProjectID, + Config.Edition == EDITION_SINGLE && HMML.metadata.project ? + HMML.metadata.project : + Config.ProjectID)) { CopyString(CollationBuffers->ProjectName, ProjectInfo[ProjectIndex].FullName); break; @@ -2218,8 +2316,7 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen } else { - Filename[StringLength(Filename) - StringLength(".hmml")] = '\0'; - fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", Filename, HMML.metadata.title); + fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title); hmml_free(&HMML); return RC_ERROR_HMML; } @@ -2233,8 +2330,7 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen case RC_INVALID_TEMPLATE: // Invalid template case RC_ERROR_FILE: // Could not load template case RC_ERROR_MEMORY: // Could not allocate memory for template - Filename[StringLength(Filename) - StringLength(".hmml")] = '\0'; - fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", Filename, HMML.metadata.title); + fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title); hmml_free(&HMML); return RC_ERROR_HMML; case RC_SUCCESS: @@ -2242,22 +2338,6 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen } } - CopyString(CollationBuffers->Title, HMML.metadata.title); - buffer URLPlayer; - ClaimBuffer(&URLPlayer, "URLPlayer", 2048); - char *Ptr = Filename; - Ptr += (StringLength(Filename) - StringLength(".hmml")); - if(!(StringsDiffer(Ptr, ".hmml"))) - { - *Ptr = '\0'; - ConstructPlayerURL(&URLPlayer, Filename); - *Ptr = '.'; - } - - CopyString(CollationBuffers->URLPlayer, URLPlayer.Location); - DeclaimBuffer(&URLPlayer); - CopyString(CollationBuffers->VideoID, HMML.metadata.id); - #if DEBUG printf( "================================================================================\n" @@ -2346,21 +2426,20 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen } } + if(Config.Edition != EDITION_SINGLE) + { + RewindBuffer(&CollationBuffers->Search); + CopyStringToBuffer(&CollationBuffers->Search, "name: \"%s\"\n" + "title: \"", BaseFilename); + CopyStringToBufferJSONSafe(&CollationBuffers->Search, HMML.metadata.title); + CopyStringToBuffer(&CollationBuffers->Search, "\"\n" + "markers:\n"); + } + #if DEBUG printf("\n\n --- Entering Annotations Loop ---\n\n\n\n"); #endif - RewindBuffer(&CollationBuffers->Search); - char *FilenamePtr = Filename; - FilenamePtr += StringLength(Filename) - StringLength(".hmml"); - *FilenamePtr = '\0'; - CopyStringToBuffer(&CollationBuffers->Search, "name: \"%s\"\n" - "title: \"", Filename); - *FilenamePtr = '.'; - CopyStringToBufferJSONSafe(&CollationBuffers->Search, HMML.metadata.title); - CopyStringToBuffer(&CollationBuffers->Search, "\"\n" - "markers:\n"); - for(int AnnotationIndex = 0; AnnotationIndex < HMML.annotation_count; ++AnnotationIndex) { #if DEBUG @@ -2711,14 +2790,16 @@ AppendedIdentifier: char *Speaker = Anno->quote.author ? Anno->quote.author : HMML.metadata.stream_username ? HMML.metadata.stream_username : HMML.metadata.member; if(BuildQuote(&QuoteInfo, Speaker, - Anno->quote.id) == 1) + Anno->quote.id) == RC_UNFOUND) { LogError(LOG_ERROR, "Quote #%s %d not found: %s:%d", Speaker, Anno->quote.id, Filename, Anno->line); - fprintf(stderr, "%s:%d: Quote #%s %d not found. Skipping this file...\n", - Filename, - Anno->line, + Filename[StringLength(Filename) - StringLength(".hmml")] = '\0'; + + fprintf(stderr, "Quote #%s %d not found\n" + "\e[1;31mSkipping\e[0m %s - %s\n", Speaker, - Anno->quote.id); + Anno->quote.id, + BaseFilename, HMML.metadata.title); hmml_free(&HMML); return RC_ERROR_QUOTE; } @@ -3486,7 +3567,15 @@ BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, i switch(Template->Metadata.Tag[i].TagCode) { case TAG_PROJECT: - CopyStringToBuffer(&Output, CollationBuffers->ProjectName); + if(CollationBuffers->ProjectName[0] == '\0') + { + fprintf(stderr, "Template contains a tag\n" + "Skipping just this tag, because we do not know the project's full name\n"); + } + else + { + CopyStringToBuffer(&Output, CollationBuffers->ProjectName); + } break; case TAG_TITLE: CopyStringToBuffer(&Output, CollationBuffers->Title); @@ -3498,7 +3587,16 @@ BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, i CopyStringToBuffer(&Output, CollationBuffers->VideoID); break; case TAG_INDEX: - CopyBuffer(&Output, &CollationBuffers->Index); + if(Config.Edition == EDITION_SINGLE) + { + fprintf(stderr, "Template contains a tag\n" + "Skipping just this tag, because an index cannot be generated for inclusion in a\n" + "bespoke template in Single Edition\n"); + } + else + { + CopyBuffer(&Output, &CollationBuffers->Index); + } break; case TAG_INCLUDES: CopyBuffer(&Output, PageType == PAGE_PLAYER ? &CollationBuffers->IncludesPlayer : &CollationBuffers->IncludesIndex); @@ -4973,7 +5071,6 @@ main(int ArgC, char **Args) if(ClaimBuffer(&CollationBuffers.IncludesIndex, "IncludesIndex", Kilobytes(1)) == RC_ARENA_FULL) { goto RIP; }; if(ClaimBuffer(&CollationBuffers.Search, "Search", Kilobytes(32)) == RC_ARENA_FULL) { goto RIP; }; if(ClaimBuffer(&CollationBuffers.ScriptIndex, "ScriptIndex", 256) == RC_ARENA_FULL) { goto RIP; }; - *CollationBuffers.Title = '\0'; // NOTE(matt): Templating // @@ -5127,33 +5224,51 @@ NextFile: // TODO(matt): Just change the default output location so all these guys won't overwrite each other for(int FileIndex = optind; FileIndex < ArgC; ++FileIndex) { - switch(HMMLToBuffers(&CollationBuffers, &BespokeTemplate, Args[FileIndex])) + bool HasBespokeTemplate = FALSE; + char *Ptr = Args[FileIndex]; + Ptr += (StringLength(Args[FileIndex]) - StringLength(".hmml")); + if(!(StringsDiffer(Ptr, ".hmml"))) { - // TODO(matt): Actually sort out the fatality of these cases, once we are always-on - case RC_ERROR_FILE: - case RC_ERROR_FATAL: - goto RIP; - case RC_ERROR_HMML: - case RC_ERROR_MAX_REFS: - case RC_ERROR_QUOTE: - case RC_INVALID_REFERENCE: - if(FileIndex < (ArgC - 1)) { goto NextFile; } - else { goto RIP; } - case RC_SUCCESS: - break; - }; - switch(BuffersToHTML(&CollationBuffers, PlayerTemplate, 0, PAGE_PLAYER)) - { - // TODO(matt): Actually sort out the fatality of these cases, once we are always-on - case RC_INVALID_TEMPLATE: - LogError(LOG_ERROR, "Invalid player template: %s", PlayerTemplate->Metadata.Filename); - case RC_ERROR_MEMORY: - case RC_ERROR_FILE: - case RC_ARENA_FULL: - goto RIP; - case RC_SUCCESS: - break; - }; + CopyString(Config.SingleHMMLFilePath, Args[FileIndex]); + switch(HMMLToBuffers(&CollationBuffers, &BespokeTemplate, Args[FileIndex])) + { + // TODO(matt): Actually sort out the fatality of these cases, once we are always-on + case RC_ERROR_FILE: + case RC_ERROR_FATAL: + goto RIP; + case RC_ERROR_HMML: + case RC_ERROR_MAX_REFS: + case RC_ERROR_QUOTE: + case RC_INVALID_REFERENCE: + if(FileIndex < (ArgC - 1)) { goto NextFile; } + else { goto RIP; } + case RC_SUCCESS: + break; + }; + + HasBespokeTemplate = StringsDiffer(BespokeTemplate->Metadata.Filename, ""); + + switch(BuffersToHTML(&CollationBuffers, + HasBespokeTemplate ? BespokeTemplate : PlayerTemplate, + 0, + PAGE_PLAYER)) + { + // TODO(matt): Actually sort out the fatality of these cases, once we are always-on + case RC_INVALID_TEMPLATE: + if(HasBespokeTemplate) { DeclaimTemplate(BespokeTemplate); } + if(FileIndex < (ArgC - 1)) { goto NextFile; } + case RC_ERROR_MEMORY: + case RC_ERROR_FILE: + case RC_ARENA_FULL: + goto RIP; + case RC_SUCCESS: +#if 0 + fprintf(stdout, "\e[1;32mWritten\e[0m %s\n", HasBespokeTemplate ? Config.OutIntegratedLocation : Config.OutLocation); +#endif + if(HasBespokeTemplate) { DeclaimTemplate(BespokeTemplate); } + break; + }; + } } }