/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* ** In jurisdictions which permit it, this software is released into ** the Public Domain by its author, Stephan Beal ** (https://wanderinghorse.net/home/stephan/). In place of a legal ** notice, here is a non-binding blessing: ** ** * May you do good and not evil. ** * May you find forgiveness for yourself and forgive others. ** * May you share freely, never taking more than you give. ** ** ** In jurisdictions which do not acknolwdge Public Domain, or ** optionally in all jurisdictions (as you prefer), it may be used ** under the terms of the Simplified BSD License: ** ** Copyright (c) 2021, 2022 Stephan Beal ** (https://wanderinghorse.net/home/stephan/) ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License"). ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ******************************************************************************* ** ** This application reads in Fossil SCM (https://fossil-scm.org) skin ** configuration files and emits them in a form suitable for importing ** directly into a fossil database using the (fossil config import) ** command. ** ** As input it requires one or more skin configuration files (css.txt, ** header.txt, footer.txt, details.txt, js.txt) and all output goes to ** stdout unless redirected using the -o FILENAME flag. ** ** Run it with no arguments or one of (help, --help, -?) for help text. */ #include #include #include #include #include #include static struct App_ { const char * argv0; time_t now; FILE * ostr; } App = { 0, 0, 0 }; static void err(const char *zFmt, ...){ va_list vargs; va_start(vargs, zFmt); fputs("ERROR: ",stderr); vfprintf(stderr, zFmt, vargs); fputc('\n', stderr); va_end(vargs); } static void app_usage(int isErr){ FILE * const ios = isErr ? stderr : stdout; fprintf(ios, "Usage: %s ?OPTIONS? input-filename...\n\n", App.argv0); fprintf(ios, "Each filename must be one file which is conventionally " "part of a Fossil SCM skin set:\n" " css.txt, header.txt, footer.txt, details.txt, js.txt\n"); fprintf(ios, "\nOptions:\n"); fprintf(ios, "\n\t-o FILENAME = send output to the given file. " "'-' means stdout (the default).\n"); fputc('\n', ios); } /* ** Reads file zFilename, stores its contents in *zContent, and sets the ** length of its contents to *nContent. ** ** Returns 0 on success. On error, *zContent and *nContent are not ** modified and it may emit a message describing the problem. */ int read_file(char const *zFilename, unsigned char ** zContent, int * nContent){ long fpos; int rc = 0; unsigned char * zMem = 0; FILE * f = fopen(zFilename, "rb"); if(!f){ err("Cannot open file %s. Errno=%d", zFilename, errno); return errno; } fseek(f, 0L, SEEK_END); rc = errno; if(rc){ err("Cannot seek() file %s. Errno=%d", zFilename, rc); goto end; } fpos = ftell(f); fseek(f, 0L, SEEK_SET); zMem = (unsigned char *)malloc((size_t)fpos + 1); if(!zMem){ err("Malloc failed."); rc = ENOMEM; goto end; } zMem[fpos] = 0; if(fpos && (size_t)1 != fread(zMem, (size_t)fpos, 1, f)){ rc = EIO; err("Error #%d reading file %s", rc, zFilename); goto end; } end: fclose(f); if(rc){ free(zMem); }else{ *zContent = zMem; *nContent = fpos; } return rc; } /* ** Expects zFilename to be one of the conventional skin filename ** parts. This routine converts it to config format and emits it to ** App.ostr. */ int dispatch_file(char const *zFilename){ const char * zKey = 0; int nContent = 0, nContent2 = 0, nOut = 0, nTime = 0, rc = 0; time_t theTime = App.now; unsigned char * zContent = 0; unsigned char * z = 0; if(strstr(zFilename, "css.txt")){ zKey = "css"; }else if(strstr(zFilename, "header.txt")){ zKey = "header"; }else if(strstr(zFilename, "footer.txt")){ zKey = "footer"; }else if(strstr(zFilename, "details.txt")){ zKey = "details"; }else if(strstr(zFilename, "js.txt")){ zKey = "js"; }else { err("Cannot determine skin part from filename: %s", zFilename); return 1; } rc = read_file(zFilename, &zContent, &nContent); if(rc) return rc; for( z = zContent; z < zContent + nContent; ++z ){ /* Count file content length with ' characters doubled */ nContent2 += ('\'' == *z) ? 2 : 1; } while(theTime > 0){/* # of digits in time */ ++nTime; theTime /= 10; } fprintf(App.ostr, "config /config %d\n", (int)(nTime + 12/*"value"+spaces+quotes*/ + (int)strlen(zKey) + nContent2)); fprintf(App.ostr, "%d '%s' value '", (int)App.now, zKey); for( z = zContent; z < zContent + nContent; ++z ){ /* Emit file content with ' characters doubled */ if('\'' == (char)*z){ fputc('\'', App.ostr); } fputc((char)*z, App.ostr); } free(zContent); fprintf(App.ostr, "'\n"); return 0; } int main(int argc, char const * const * argv){ int rc = 0, i ; App.argv0 = argv[0]; App.ostr = stdout; if(argc<2){ app_usage(1); rc = 1; goto end; } App.now = time(0); for( i = 1; i < argc; ++i ){ const char * zArg = argv[i]; if(0==strcmp(zArg,"help") || 0==strcmp(zArg,"--help") || 0==strcmp(zArg,"-?")){ app_usage(0); rc = 0; break; }else if(0==strcmp(zArg,"-o")){ /* -o OUTFILE (- == stdout) */ ++i; if(i==argc){ err("Missing filename for -o flag"); rc = 1; break; }else{ const char *zOut = argv[i]; if(App.ostr != stdout){ err("Cannot specify -o more than once."); rc = 1; break; } if(0!=strcmp("-",zOut)){ FILE * o = fopen(zOut, "wb"); if(!o){ err("Could not open file %s for writing. Errno=%d", zOut, errno); rc = errno; break; } App.ostr = o; } } }else if('-' == zArg[0]){ err("Unhandled argument: %s", zArg); rc = 1; break; }else{ rc = dispatch_file(zArg); if(rc) break; } } end: if(App.ostr != stdout){ fclose(App.ostr); } return rc ? EXIT_FAILURE : EXIT_SUCCESS; }