diff options
Diffstat (limited to 'src/rep.c')
-rw-r--r-- | src/rep.c | 703 |
1 files changed, 703 insertions, 0 deletions
diff --git a/src/rep.c b/src/rep.c new file mode 100644 index 0000000..b333ef5 --- /dev/null +++ b/src/rep.c @@ -0,0 +1,703 @@ +/* + * AUTHOR + * N. Nielsen + * + * LICENSE + * This software is in the public domain. + * + * The software is provided "as is", without warranty of any kind, + * express or implied, including but not limited to the warranties + * of merchantability, fitness for a particular purpose, and + * noninfringement. In no event shall the author(s) be liable for any + * claim, damages, or other liability, whether in an action of + * contract, tort, or otherwise, arising from, out of, or in connection + * with the software or the use or other dealings in the software. + * + * SUPPORT + * Send bug reports to: <nielsen@memberwebs.com> + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include <errno.h> +#include "common/usuals.h" +#include "common/compat.h" +#include "lib/rlib.h" +#include "lib/rep.h" +#include "common/repfile.h" +#include "file.h" + +/* Amount of context that will be shown in a prompt surrounding the text */ +#define CONFIRM_CONTEXT 0x20 + +/* Extension used for backup files */ +const char* kBackupExt = ".x_r"; + + + +/* ------------------------------------------------------------------------ + * GLOBALS + */ + +/* + * Since WIN32 has a hokey (did I say hokey, I meant crappy) shell + * we add a bit more file selection functionality if building there. + * We allow a set wildcards to be specified. These apply when recursing + * folders. + */ +#ifdef _WIN32 + #define MAX_WILDCARDS 0x20 + const char* kAllFiles = "*.*"; + + /* When recursing through folders this is the set of files to replace */ + char* g_wildcards[MAX_WILDCARDS]; +#endif + +/* Quiet mode or not? */ +bool g_showStatus = true; + +/* Backups or not? */ +bool g_keepBackups = false; + +/* + * For status updates the amount of replacements and the amount of + * files seen + */ +long g_totalReplaces = 0; +long g_totalFiles = 0; + + +/* ------------------------------------------------------------------------ + * FORWARD DECLARATIONS + */ + +/* The two match callbacks */ +int matchConfirm(r_stream* stream, r_replace* repl); +int matchStatus(r_stream* stream, r_replace* repl); + +/* The main replace functions */ +int replaceFolder(r_context* ctx, char* szWildCard); +int replaceFile(r_context* ctx, FILE* fIn, FILE* fOut); +int replaceFilename(r_context* ctx, const char* szIn, const char* szOut); +int replaceSingleFile(r_context* ctx, const char* szIn); + +/* Error stuff */ +int usage(); + + + +/* ------------------------------------------------------------------------ */ + +int main(int argc, char* argv[]) +{ + /* We use one r_stream throughout for speed */ + r_context context; + bool fileMode = false; + int ch = 0; + + + /* + * Function return values throughout program are a bit different + * + * 0 = normal operation + * -1 = nothing happened but successful + * >0 = error code + */ + int ret = 0; + int r = R_OK; + + + /* Enough params? */ + if(argc < 2) + return usage(); + + + /* Init the stream/context */ + memset(&context, 0, sizeof(context)); + + + /* A little preparation */ + context.stream.fMatch = matchStatus; + + +#ifdef _WIN32 + *g_wildcards = NULL; + + while((ch = getopt(argc, argv, "bcipqw:z:")) != -1) +#else + while((ch = getopt(argc, argv, "bcipqz:")) != -1) +#endif + { + switch(ch) + { + /* Use backups */ + case 'b': + g_keepBackups = true; + break; + + case 'i': + fileMode = true; + break; + + /* Confirmation mode */ + case 'c': + context.stream.fMatch = matchConfirm; + break; + + /* Quiet mode */ + case 'q': + g_showStatus = false; + break; + + /* Parse mode */ + case 'p': + context.options |= RLIB_MODE_PARSER; + break; + +#ifdef _WIN32 + case 'w': + { + /* Get all the wildcards out */ + size_t len = 0; + char* arg = optarg; + char** wildcards = g_wildcards; + + /* + * Enumerate the wild cards seperating them + * at semi-colons or colons + */ + while((len = strcspn(arg, ";")) + && (wildcards < (g_wildcards + MAX_WILDCARDS))) + { + bool last = (arg[len] == '\0'); + + arg[len] = '\0'; + *wildcards = arg; + + wildcards++; + arg += len; + arg++; + + if(last) break; + } + + /* Null terminate the array */ + *wildcards = NULL; + } + break; +#endif + + case 'z': + context.block = atoi(optarg); + if(context.block <= 0x20) + errx(2, "invalid argument. specify block size greater than 32."); + break; + + case '?': + default: + return usage(); + break; + } + } + + argc -= optind; + argv += optind; + + +#ifdef _WIN32 + if(*g_wildcards == NULL) + { + g_wildcards[0] = "*.*"; + g_wildcards[1] = NULL; + } +#endif + + + /* + * The next param should be the script file + * so read it... + */ + if(argc > 0) + { + FILE* file = fopen(argv[0], "rb"); + if(!file) + err(1, "couldn't open rep script file: %s", argv[0]); + + r = repLoad(&context, file); + fclose(file); + + if(r < 0) + exit(errmsg(r, &(context.script))); + } + else + return usage(); + + /* + * Now initialize the rlib library structures + */ + r = repInit(&context); + if(r < 0) + exit(errmsg(r, NULL)); + + argc--; + argv++; + + + /* + * Okay now we have two options: + * + * If -f was specified we read each argument and replace the file in place, + * possibly with backups, descend into folders etc... + * + * Or if not specified we read from the in file and output to the outfile + * If out or both in and out are not specified then use stdin/stdout + */ + if(!fileMode) + { + char* in = "-"; + char* out = "-"; + int ret; + + if(argc > 2) + usage(); + if(argc > 0) + in = argv[0]; + if(argc > 1) + out = argv[1]; + + ret = rlibSetVar(&(context.stream), "FILENAME", in); + if(ret < 0) + ret = errmsg(ret, &(context.script)); + else + ret = replaceFilename(&context, in, out); + } + else + { + while(argc > 0) + { + if(isDirectory(argv[0])) + ret = replaceFolder(&context, argv[0]); + else + ret = replaceSingleFile(&context, argv[0]); + + if(ret > 0) + break; + + argc--; + argv++; + } + } + + /* Release the stream */ + repFree(&context); + + /* Give some stats */ + if(ret == 0 && g_showStatus) + fprintf(stderr, "\n[%d replacements in %d files]\n", g_totalReplaces, g_totalFiles); + + /* Done! */ + return ret <= 0 ? 0 : ret; +} + + + +/* replaceFolder: -------------------------------------------------------------- + * Enumerate all files in a folder and do a replace on each one using + * backups (Note: this function is called recursively) + */ +int replaceFolder(r_context* ctx, char* folder) +{ + struct dirent ent; + char old[260]; /* Save the current folder (as we'll change it) */ + int ret = 0; + DIR* dir; + + /* + * We use a null wildcard as the default and replace below + * if running under WIN32 + */ + char* wildcard = NULL; + char** wildcards = &wildcard; + + + /* Backup the current directory */ + if(!getcwd(old, 260)) + err(1, "couldn't get working folder"); + + +#ifdef _WIN32 + /* + * Now loop through all the wildcards and search for them + * in the folder + */ + wildcards = g_wildcards; + while(*wildcards) + { +#endif + dir = dir_first(folder, *wildcards, &ent); + + if(dir == INVALID_DIR && errno != ENOENT) + err(1, "couldn't list path: %s", folder); + + /* Any files */ + if(dir != INVALID_DIR) + { + /* Change dir to the folder we're searching in */ + chdir(folder); + + /* And for every file */ + do + { + if(ent.d_type & DT_DIR) + { + int r = 0; + + if(g_showStatus) + fprintf(stderr, "%s ", ent.d_name); + + r = replaceSingleFile(ctx, ent.d_name); + + if(g_showStatus) + fputc('\n', stderr); + + if(r > 0) + return r; + if(r > ret) + ret = r; + } + + } + while(dir_next(dir, *wildcards, &ent)); + + if(errno != 0 && errno != ENOENT) + err(1, "couldn't list path: %s", folder); + + dir_close(dir); + + /* + * Change directory back to where we saved it + * the reason we do this is that in some cases (sub-folders) + */ + _chdir(old); + } + +#ifdef _WIN32 + wildcards++; + } +#endif + + /* Now we enumerate all the folders */ + dir = dir_first(folder, NULL, &ent); + + if(dir == INVALID_DIR && errno != ENOENT) + err(1, "couldn't list path: %s", folder); + + /* Any files? */ + if(dir != INVALID_DIR) + { + /* Go into folder */ + chdir(folder); + + do + { + /* For each folder... */ + if(ent.d_type & DT_DIR) + { + int r = 0; + + /* ... that's not dots ... */ + if(isDots(ent.d_name)) + continue; + + /* ... recursively call ourselves */ + ret = replaceFolder(ctx, ent.d_name); + + if(r > 0) + return r; + if(r > ret) + ret = r; + } + } + while(dir_next(dir, NULL, &ent)); + + if(errno != 0 && errno != ENOENT) + err(1, "couldn't list path: %s", folder); + + dir_close(dir); + + /* Change directory back */ + chdir(old); + } + + /* Did anything change? */ + return ret; +} + + +/* replaceSingleFile: ------------------------------------------------------- + * Replace a file into itself using temp/backups + */ +int replaceSingleFile(r_context* ctx, const char* file) +{ + int ret = 2; + char temp [260 + 4]; /* Temp buffer for filenames */ + + ret = rlibSetVar(&(ctx->stream), "FILENAME", file); + if(ret < 0) + return errmsg(ret, &(ctx->script)); + + /* Make a temp/backup name */ + strcpy(temp, file); + strcat(temp, kBackupExt); + + /* Now we do this 2 ways, if we're keeping backups then... */ + if(g_keepBackups) + { + /* Rename the original file to the backup */ + if(rename(file, temp)) + err(1, "couldn't make backup file: %s", temp); + + /* + * Do a replacement operation back to the original + * from the backup + */ + ret = replaceFilename(ctx, temp, file); + + /* If there were no replacements */ + if(ret == -1) + { + /* Remove the replacement file */ + unlink(file); + + /* And rename the backup file back */ + if(rename(temp, file)) + err(1, "couldn't rename file: %s", file); + } + } + + /* No backups, much faster! */ + else + { + /* Do a replacement operation to a temp file */ + ret = replaceFilename(ctx, file, temp); + + /* If there were replacements */ + if(ret == 0) + { + /* Remove original file */ + unlink(file); + + /* Rename temp to original */ + if(rename(temp, file)) + err(1, "couldn't rename file: %s", file); + } + + /* If no replacements */ + else + { + /* Remove temp file */ + unlink(temp); + } + } + + return ret; +} + + +/* replaceFilename: ----------------------------------------------------------- + * Replace one file into another + */ +int replaceFilename(r_context* ctx, const char* in, const char* out) +{ + FILE* fIn; + FILE* fOut; + int ret; + + if(!strcmp(in, "-")) + { + /* + * If someone specified stdin as the input make sure we're + * not going to put up confirmations as we need input from + * stdin ie: YNA or whatever key + */ + if(ctx->stream.fMatch == matchConfirm) + errx(2, "can't confirm replacements while using stdin as input"); + + /* Input is stdin */ + fIn = stdin; + } + + /* Input is a file */ + else + { +#ifdef _WIN32 + if(!(fIn = fopen(in, "rb"))) +#else + if(!(fIn = fopen(in, "r"))) +#endif + err(1, "couldn't open input file: %s", in); + } + + /* Output is stdout */ + if(!strcmp(out, "-")) + fOut = stdout; + + /* Output is a file */ +#ifdef _WIN32 + else if(!(fOut = fopen(out, "wb"))) +#else + else if(!(fOut = fopen(out, "w"))) +#endif + err(1, "couldn't open output file: %s", in); + + + /* Do replacements! */ + ret = replaceFile(ctx, fIn, fOut); + + /* Close any files */ + if(fIn != stdin) + fclose(fIn); + if(fOut != stdout) + fclose(fOut); + + return ret; +} + + + +/* matchConfirm: ----------------------------------------------------------------- + * Confirmation callback for rlib. Used when -i flag is given + */ +int matchConfirm(r_stream* stream, r_replace* repl) +{ + /* Remember if someone pressed A */ + static bool all = false; + char ch = 0; + int ret; + size_t context_start, context_end; + + if(all) + return 1; + + fprintf(stderr, "\n------------- ORIGINAL TEXT between ~{ and }~ markers ---------------\n"); + + /* Figure out if we have some context before replacement */ + /* and how much */ + context_start = 0; + if(repl->from >= CONFIRM_CONTEXT) + context_start = repl->from - CONFIRM_CONTEXT; + + + /* Figure out if we have context after replacement */ + context_end = stream->availIn; + + if(stream->availIn >= repl->from + repl->flen + CONFIRM_CONTEXT) + context_end = repl->from + repl->flen + CONFIRM_CONTEXT; + + /* Write Context Before */ + fwrite(stream->nextIn + context_start, sizeof(char), + repl->from - context_start, stderr); + /* Write our tag */ + fprintf(stderr, "~{"); + /* Write from text */ + fwrite(stream->nextIn + repl->from, sizeof(char), repl->flen, stderr); + + /* Write tag */ + fprintf(stderr, "}~"); + /* Write Context After */ + fwrite(stream->nextIn + repl->from + repl->flen, sizeof(char), + context_end - (repl->from + repl->flen), stderr); + /* Separator */ + fprintf(stderr, "\n----------------------------- NEW TEXT -------------------------------\n"); + // Replace with this text + fwrite(repl->to, sizeof(byte), repl->tlen, stderr); + /* Prompt */ + fprintf(stderr, "\n----------------------------------------------------------------------\n"); + + fprintf(stderr, "(YES\\No\\All)? "); + + setbuf(stdin, NULL); + while(ch != 'y' && ch != 'n' && ch != 'a' && ch != '\r') + ch = tolower(getchar()); + + if(ch == 'a') + stream->fMatch = matchStatus; + + fprintf(stderr, "\r \r"); + + ret = (ch == 'y' || ch == 'a' || ch == '\r') ? true : false; + + if(ret) + g_totalReplaces++; + + return ret; +} + + +/* matchStatus: ---------------------------------------------------------- + * Confirmation function for rlib. Used for status display + */ +int matchStatus(r_stream* stream, r_replace* repl) +{ + if(g_showStatus) + fputc('.', stderr); + + g_totalReplaces++; + + return 1; +} + + +/* outputMsg: -------------------------------------------------------------- + * Callback for rlib when a message goes to the console. + */ +void outputMsg(struct r_stream* stream, const char* data) +{ + fprintf(stderr, g_showStatus ? "\n%s " : "%s\n", data); +} + +/* replaceFile: ------------------------------------------------------------ + * Replace one file into another using file handles. This is where + * the main stuff actually happens. + */ +int replaceFile(r_context* ctx, FILE* fIn, FILE* fOut) +{ + int ret; + + ctx->stream.fMessage = outputMsg; + ctx->stream.total = 0; + + ret = repFiles(ctx, fIn, fOut); + + /* fatal errors */ + if(ret < 0) + return errmsg(ret, &(ctx->script)); + + ret = (ctx->stream.total > 0) ? 0 : -1; + + g_totalFiles++; + rlibClear(&(ctx->stream)); + + return ret; +} + +/* usage: ------------------------------------------------------------- + * Display usage info + */ +int usage() +{ +#ifdef _WIN32 + fprintf(stderr, "usage: rep [-cpqx] [ -z buffsize ] script infile outfile\n"); + fprintf(stderr, " rep -i [-bcpqx] [ -z buffsize ] [ -w wildcard ] script file ...\n"); +#else + fprintf(stderr, "usage: rep [-cpq] [ -z buffsize ] script infile outfile\n"); + fprintf(stderr, " rep -i [-bcpq] [ -z buffsize ] script file ...\n"); +#endif + return 2; +} + |