#if 0 ctime -begin ${0%.*}.ctm gcc -g -fsanitize=address $0 -o ${0%.*} hmml.a ctime -end ${0%.*}.ctm exit #endif #include "hmmlib.h" #include #include #include #ifndef bool typedef unsigned int bool; #endif #define TRUE 1 #define FALSE 0 typedef struct { unsigned int UniqueRefs; char RefID[1024][32]; } ref_info; typedef struct { char *Location; char *Ptr; int Size; } buffer; void ClaimBuffer(char *MemoryArena, int *ClaimedMemory, buffer *Buffer, int Size) { Buffer->Location = MemoryArena + *ClaimedMemory; Buffer->Size = Size; *ClaimedMemory += Buffer->Size; Buffer->Ptr = Buffer->Location; } int StringLength(char *String) { int i = 0; while(String[i]) { ++i; } return i; } char ToLower(char Char) { if(Char >= 'A' && Char <= 'Z') { Char += 'a' - 'A'; } return Char; } int StringsDiffer(char *A, char *B) { while(*A && *B && ToLower(*A) == ToLower(*B)) { ++A, ++B; } return *A - *B; } __attribute__ ((format (printf, 2, 3))) int CopyStringToBuffer(buffer *Dest, char *Format, ...) { va_list Args; va_start(Args, Format); int Length = vsprintf(Dest->Ptr, Format, Args); va_end(Args); Dest->Ptr += Length; return Length; } int main(int ArgC, char **Args) { if(ArgC < 2) { fprintf(stderr, "Usage: %s filename(s)\n", Args[0]); return 1; } // Init MemoryArena int ArenaSize = 1024 * 64; char *MemoryArena; if(!(MemoryArena = calloc(ArenaSize, 1))) { perror(Args[0]); return 1; } int ClaimedMemory = 0; // Buffers and Pointers char *InPtr; // Associated buffer is allocated by hmml_parse_file() buffer Errors; buffer Out; FILE *InFile; for(int FileIndex = 1; FileIndex < ArgC; ++FileIndex) { if(!(InFile = fopen(Args[FileIndex], "r"))) { perror(Args[0]); free(MemoryArena); return 1; } ref_info Refs = { 0 }; ClaimBuffer(MemoryArena, &ClaimedMemory, &Errors, 1024 * 32); char OutFilename[StringLength(Args[FileIndex]) + 5]; sprintf(OutFilename, "%s.txt", Args[FileIndex]); HMML_Output HMML = hmml_parse_file(InFile); fclose(InFile); if(HMML.well_formed) { ClaimBuffer(MemoryArena, &ClaimedMemory, &Out, 1024 * 32); char *Member = HMML.metadata.stream_username ? HMML.metadata.stream_username : HMML.metadata.member; bool WritingChat = TRUE; for(int BuildPass = 0; BuildPass < 2; ++BuildPass) { int OutLine = 1, OutColumn = 1; Errors.Ptr = Errors.Location; HMML_Annotation *Anno; #if 0 if(WritingChat == FALSE) { CopyStringToBuffer(&Errors, "%s - %ld characters over budget. Shrinking...\n", Args[FileIndex], (Out.Ptr - Out.Location) - 5000); } #endif Out.Ptr = Out.Location; for(int AnnotationIndex = 0; AnnotationIndex < HMML.annotation_count; ++AnnotationIndex) { Anno = HMML.annotations + AnnotationIndex; if(Anno->author && !WritingChat) { goto skip; } OutColumn += CopyStringToBuffer(&Out, "%s ", Anno->time); InPtr = Anno->text; if(Anno->author) { OutColumn += CopyStringToBuffer(&Out, "Chat comment: \""); } int MarkerIndex = 0, RefIndex = 0, InOffset = 0; while(*InPtr || RefIndex < Anno->reference_count) { if(MarkerIndex < Anno->marker_count && InPtr - Anno->text == Anno->markers[MarkerIndex].offset) { char *Readable = Anno->markers[MarkerIndex].parameter ? Anno->markers[MarkerIndex].parameter : Anno->markers[MarkerIndex].marker; if(Anno->markers[MarkerIndex].type == HMML_MEMBER && (!StringsDiffer(Anno->markers[MarkerIndex].marker, Member))) { InPtr += StringLength(Readable); while(*InPtr && ( *InPtr == ',' || *InPtr == ':' || *InPtr == ' ' ) ) { ++InPtr; } if(InPtr - Anno->text > 0 && !InOffset) { InOffset = InPtr - Anno->text; } } ++MarkerIndex; } if(RefIndex < Anno->reference_count && InPtr - Anno->text == Anno->references[RefIndex].offset) { char *RefID; if(Anno->references[RefIndex].isbn) { RefID = Anno->references[RefIndex].isbn; } else if(Anno->references[RefIndex].url) { RefID = Anno->references[RefIndex].url; } else { fprintf(stderr, "%s: Reference must contain an ISBN or URL\n", Args[FileIndex]); hmml_free(&HMML); free(MemoryArena); return 1; } int i; for(i = 0; i < Refs.UniqueRefs; ++i) { if(!StringsDiffer(Refs.RefID[i], RefID)) { goto SkipRef; } } sprintf(Refs.RefID[i], RefID); ++Refs.UniqueRefs; if(Anno->references[RefIndex].offset == InOffset) { if(Out.Ptr[-1] != '"' && Out.Ptr[-1] != ' ') { *Out.Ptr++ = ' '; OutColumn += 1; } if(Anno->references[RefIndex].page) { OutColumn += CopyStringToBuffer(&Out, "%s", Anno->references[RefIndex].page); } else if(Anno->references[RefIndex].site) { OutColumn += CopyStringToBuffer(&Out, "%s", Anno->references[RefIndex].site); } else if(Anno->references[RefIndex].title) { OutColumn += CopyStringToBuffer(&Out, "%s", Anno->references[RefIndex].title); } } if(Anno->references[RefIndex].url) { if(Anno->references[RefIndex].offset < StringLength(Anno->text) || RefIndex < Anno->reference_count-1) { if(Out.Ptr[-1] != ' ') { *Out.Ptr++ = ' '; OutColumn += 1; } OutColumn += CopyStringToBuffer(&Out, "- %s -", Anno->references[RefIndex].url); } else { if(Out.Ptr[-1] == ' ') { --Out.Ptr; OutColumn -= 1; } if(InPtr[-3] != ':') { OutColumn += CopyStringToBuffer(&Out, ": "); } OutColumn += CopyStringToBuffer(&Out, "%s", Anno->references[RefIndex].url); } } SkipRef: ++RefIndex; } if(*InPtr) { switch(*InPtr) { case '<': OutColumn += CopyStringToBuffer(&Out, "<"); break; case '>': OutColumn += CopyStringToBuffer(&Out, ">"); break; case '#': CopyStringToBuffer(&Errors, "%s:%d,%d - Removed '#'. Consider editing\n", OutFilename, OutLine, OutColumn); break; default: *Out.Ptr++ = *InPtr; OutColumn += 1; break; } ++InPtr; } } if(Anno->author && WritingChat == TRUE) { *Out.Ptr++ = '\"'; } *Out.Ptr++ = '\n'; ++OutLine; OutColumn = 1; skip: {}; } // TODO(matt): Handle multiple annotators CopyStringToBuffer(&Out, "\nAnnotated by %s - https://handmade.network/m/%s\n", HMML.metadata.annotators[0], HMML.metadata.annotators[0]); if(Out.Ptr - Out.Location > 5000) { WritingChat = FALSE; } else { break; } } if(Out.Ptr - Out.Location > 5000) { CopyStringToBuffer(&Errors, "%s - %ld characters over budget. Requires manual shrinking!\n", OutFilename, (Out.Ptr - Out.Location) - 5000); } // NOTE(matt): At this point we should have filled a buffer with the stuff hmml_free(&HMML); FILE *OutFile; if(!(OutFile = fopen(OutFilename, "w"))) { perror(Args[0]); free(MemoryArena); return 1; } fwrite(Out.Location, Out.Ptr - Out.Location, 1, OutFile); fclose(OutFile); ClaimedMemory -= Out.Size; } else { CopyStringToBuffer(&Errors, "%s:%d: %s\n", Args[FileIndex], HMML.error.line, HMML.error.message); hmml_free(&HMML); } if(Errors.Ptr > Errors.Location) { fprintf(stderr, Errors.Location); } ClaimedMemory -= Errors.Size; } free(MemoryArena); return 0; }