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 = {
.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 <!-- __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;
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 <!-- __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;
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;
};
}
}
}