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
This commit is contained in:
Matt Mascarenhas 2018-01-04 20:55:51 +00:00
parent 8f37ce0b76
commit c1d6c87746
1 changed files with 189 additions and 74 deletions

View File

@ -14,7 +14,7 @@ typedef struct
version CINERA_APP_VERSION = { version CINERA_APP_VERSION = {
.Major = 0, .Major = 0,
.Minor = 5, .Minor = 5,
.Patch = 17 .Patch = 18
}; };
// TODO(matt): Copy in the DB 3 stuff from cinera_working.c // 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 *PlayerLocation; // Relative to Base{Dir,URL}
char *PlayerURLPrefix; /* NOTE(matt): This will become a full blown customisable output URL. char *PlayerURLPrefix; /* NOTE(matt): This will become a full blown customisable output URL.
For now it simply replaces the ProjectID */ For now it simply replaces the ProjectID */
// Single Edition - Input
char SingleHMMLFilePath[256];
// Single Edition - Output // Single Edition - Output
char *OutLocation; char *OutLocation;
@ -758,10 +760,48 @@ enum
TEMPLATE_BESPOKE TEMPLATE_BESPOKE
} templates; } 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 void
ConstructTemplatePath(template *Template, int TemplateType) 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] != '/') if(Template->Metadata.Filename[0] != '/')
{ {
char Temp[256]; char Temp[256];
@ -769,7 +809,14 @@ ConstructTemplatePath(template *Template, int TemplateType)
char *Ptr = Template->Metadata.Filename; char *Ptr = Template->Metadata.Filename;
if(TemplateType == TEMPLATE_BESPOKE) 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 else
{ {
@ -1633,10 +1680,10 @@ BuildQuote(quote_info *Info, char *Speaker, int ID)
int CacheSize = QuoteStaging.Ptr - QuoteStaging.Location; int CacheSize = QuoteStaging.Ptr - QuoteStaging.Location;
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); FreeBuffer(&QuoteStaging);
return 1; return RC_UNFOUND;
} }
} }
} }
@ -1645,14 +1692,14 @@ BuildQuote(quote_info *Info, char *Speaker, int ID)
CurlQuotes(&QuoteStaging, QuotesURL); CurlQuotes(&QuoteStaging, QuotesURL);
int CacheSize = QuoteStaging.Ptr - QuoteStaging.Location; int CacheSize = QuoteStaging.Ptr - QuoteStaging.Location;
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); FreeBuffer(&QuoteStaging);
return 1; return RC_UNFOUND;
} }
} }
return 0; return RC_SUCCESS;
} }
int int
@ -2176,6 +2223,8 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen
RewindBuffer(&CollationBuffers->ScriptPlayer); RewindBuffer(&CollationBuffers->ScriptPlayer);
RewindBuffer(&CollationBuffers->IncludesIndex); RewindBuffer(&CollationBuffers->IncludesIndex);
RewindBuffer(&CollationBuffers->ScriptIndex); RewindBuffer(&CollationBuffers->ScriptIndex);
*CollationBuffers->Title = '\0';
*CollationBuffers->ProjectName = '\0';
char Filepath[256]; char Filepath[256];
if(Config.Edition == EDITION_PROJECT) if(Config.Edition == EDITION_PROJECT)
@ -2200,9 +2249,58 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen
if(HMML.well_formed) 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) 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); CopyString(CollationBuffers->ProjectName, ProjectInfo[ProjectIndex].FullName);
break; break;
@ -2218,8 +2316,7 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen
} }
else else
{ {
Filename[StringLength(Filename) - StringLength(".hmml")] = '\0'; fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title);
fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", Filename, HMML.metadata.title);
hmml_free(&HMML); hmml_free(&HMML);
return RC_ERROR_HMML; return RC_ERROR_HMML;
} }
@ -2233,8 +2330,7 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen
case RC_INVALID_TEMPLATE: // Invalid template case RC_INVALID_TEMPLATE: // Invalid template
case RC_ERROR_FILE: // Could not load template case RC_ERROR_FILE: // Could not load template
case RC_ERROR_MEMORY: // Could not allocate memory for 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", BaseFilename, HMML.metadata.title);
fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", Filename, HMML.metadata.title);
hmml_free(&HMML); hmml_free(&HMML);
return RC_ERROR_HMML; return RC_ERROR_HMML;
case RC_SUCCESS: 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 #if DEBUG
printf( printf(
"================================================================================\n" "================================================================================\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 #if DEBUG
printf("\n\n --- Entering Annotations Loop ---\n\n\n\n"); printf("\n\n --- Entering Annotations Loop ---\n\n\n\n");
#endif #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) for(int AnnotationIndex = 0; AnnotationIndex < HMML.annotation_count; ++AnnotationIndex)
{ {
#if DEBUG #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; char *Speaker = Anno->quote.author ? Anno->quote.author : HMML.metadata.stream_username ? HMML.metadata.stream_username : HMML.metadata.member;
if(BuildQuote(&QuoteInfo, if(BuildQuote(&QuoteInfo,
Speaker, 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); 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[StringLength(Filename) - StringLength(".hmml")] = '\0';
Filename,
Anno->line, fprintf(stderr, "Quote #%s %d not found\n"
"\e[1;31mSkipping\e[0m %s - %s\n",
Speaker, Speaker,
Anno->quote.id); Anno->quote.id,
BaseFilename, HMML.metadata.title);
hmml_free(&HMML); hmml_free(&HMML);
return RC_ERROR_QUOTE; return RC_ERROR_QUOTE;
} }
@ -3486,7 +3567,15 @@ BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, i
switch(Template->Metadata.Tag[i].TagCode) switch(Template->Metadata.Tag[i].TagCode)
{ {
case TAG_PROJECT: case TAG_PROJECT:
CopyStringToBuffer(&Output, CollationBuffers->ProjectName); if(CollationBuffers->ProjectName[0] == '\0')
{
fprintf(stderr, "Template contains a <!-- __CINERA_PROJECT --> tag\n"
"Skipping just this tag, because we do not know the project's full name\n");
}
else
{
CopyStringToBuffer(&Output, CollationBuffers->ProjectName);
}
break; break;
case TAG_TITLE: case TAG_TITLE:
CopyStringToBuffer(&Output, CollationBuffers->Title); CopyStringToBuffer(&Output, CollationBuffers->Title);
@ -3498,7 +3587,16 @@ BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, i
CopyStringToBuffer(&Output, CollationBuffers->VideoID); CopyStringToBuffer(&Output, CollationBuffers->VideoID);
break; break;
case TAG_INDEX: case TAG_INDEX:
CopyBuffer(&Output, &CollationBuffers->Index); if(Config.Edition == EDITION_SINGLE)
{
fprintf(stderr, "Template contains a <!-- __CINERA_INDEX --> 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; break;
case TAG_INCLUDES: case TAG_INCLUDES:
CopyBuffer(&Output, PageType == PAGE_PLAYER ? &CollationBuffers->IncludesPlayer : &CollationBuffers->IncludesIndex); 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.IncludesIndex, "IncludesIndex", Kilobytes(1)) == RC_ARENA_FULL) { goto RIP; };
if(ClaimBuffer(&CollationBuffers.Search, "Search", Kilobytes(32)) == 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; }; if(ClaimBuffer(&CollationBuffers.ScriptIndex, "ScriptIndex", 256) == RC_ARENA_FULL) { goto RIP; };
*CollationBuffers.Title = '\0';
// NOTE(matt): Templating // 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 // TODO(matt): Just change the default output location so all these guys won't overwrite each other
for(int FileIndex = optind; FileIndex < ArgC; ++FileIndex) 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 CopyString(Config.SingleHMMLFilePath, Args[FileIndex]);
case RC_ERROR_FILE: switch(HMMLToBuffers(&CollationBuffers, &BespokeTemplate, Args[FileIndex]))
case RC_ERROR_FATAL: {
goto RIP; // TODO(matt): Actually sort out the fatality of these cases, once we are always-on
case RC_ERROR_HMML: case RC_ERROR_FILE:
case RC_ERROR_MAX_REFS: case RC_ERROR_FATAL:
case RC_ERROR_QUOTE: goto RIP;
case RC_INVALID_REFERENCE: case RC_ERROR_HMML:
if(FileIndex < (ArgC - 1)) { goto NextFile; } case RC_ERROR_MAX_REFS:
else { goto RIP; } case RC_ERROR_QUOTE:
case RC_SUCCESS: case RC_INVALID_REFERENCE:
break; if(FileIndex < (ArgC - 1)) { goto NextFile; }
}; else { goto RIP; }
switch(BuffersToHTML(&CollationBuffers, PlayerTemplate, 0, PAGE_PLAYER)) case RC_SUCCESS:
{ break;
// 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); HasBespokeTemplate = StringsDiffer(BespokeTemplate->Metadata.Filename, "");
case RC_ERROR_MEMORY:
case RC_ERROR_FILE: switch(BuffersToHTML(&CollationBuffers,
case RC_ARENA_FULL: HasBespokeTemplate ? BespokeTemplate : PlayerTemplate,
goto RIP; 0,
case RC_SUCCESS: PAGE_PLAYER))
break; {
}; // 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;
};
}
} }
} }