summaryrefslogtreecommitdiff
path: root/src/rep.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rep.c')
-rw-r--r--src/rep.c703
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;
+}
+