about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLeah Neukirchen <leah@vuxu.org>2017-09-24 18:43:33 +0200
committerLeah Neukirchen <leah@vuxu.org>2017-09-24 18:43:33 +0200
commitc188e1313bb9808bab82e0a11b358bb1ec781049 (patch)
treea7927b11d89423bc6e5293feec66ba949bc67f8e
parent53c7eebda4523eddbdc2b77e43a7cd5240b11352 (diff)
downloadxe-c188e1313bb9808bab82e0a11b358bb1ec781049.tar.gz
xe-c188e1313bb9808bab82e0a11b358bb1ec781049.tar.xz
xe-c188e1313bb9808bab82e0a11b358bb1ec781049.zip
add -p for percent rules
-rw-r--r--xe.129
-rw-r--r--xe.c111
2 files changed, 136 insertions, 4 deletions
diff --git a/xe.1 b/xe.1
index fe7bc7b..b75209d 100644
--- a/xe.1
+++ b/xe.1
@@ -7,7 +7,7 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl 0FLRnv
-.Op Fl I Ar replace-arg
+.Oo Fl p | Fl I Ar replace-arg Oc
 .Op Fl N Ar maxargs
 .Op Fl j Ar maxjobs
 .Ar command\ ...
@@ -45,8 +45,12 @@ are read from the standard input.
 The resulting command is constructed from the command line parameters,
 replacing
 .Ar replace-arg
+(unless
+.Fl p
+is used, see below)
 with the read argument, and is executed with
 .Xr execvp 3 .
+.Pp
 In this mode, no shell is involved and
 .Ar replace-arg
 must appear as a word on its own, i.e.
@@ -122,6 +126,29 @@ Dry run: don't run the resulting commands, just print them.
 .It Fl v
 Verbose: print commands to standard error before running them.
 When used twice, also print job id and exit status for each command.
+.It Fl p
+Enable
+.Em percent rules :
+this mode disables the default
+.Ar replace-arg
+substition and enables
+.Xr make 1 Ns \&- Ns
+style percent rules.
+The first argument of
+.Ar command\ ...
+is regarded as a pattern:
+in the pattern, a single occurrence of
+.Li Sq \&%
+matches one or more characters,
+and replaces the first occurrence of
+.Li Sq \&%
+with the matched string in the remaining arguments,
+which are then used as the command to be executed.
+.Pp
+Multiple runs of patterns and commands are separated by
+.Li Sq \&+ .
+Only the first matching percent rule is executed;
+when no pattern matches, no command is run.
 .It Fl I Ar replace-arg
 Replace first occurrence of
 .Ar replace-arg
diff --git a/xe.c b/xe.c
index 377dd77..1888775 100644
--- a/xe.c
+++ b/xe.c
@@ -30,7 +30,7 @@ static int maxatonce = 1;
 static int maxjobs = 1;
 static int runjobs = 0;
 static int failed = 0;
-static int Aflag, Fflag, Lflag, Rflag, aflag, nflag, vflag;
+static int Aflag, Fflag, Lflag, Rflag, aflag, nflag, pflag, vflag;
 static long iterations = 0;
 static FILE *traceout;
 static FILE *input;
@@ -252,6 +252,10 @@ run()
 			close(pipefd[1]);
 		}
 
+		if (!args[0]) {
+			fprintf(stderr, "xe: no command\n");
+			exit(126);
+		}
 		execvp(args[0], args);
 		fprintf(stderr, "xe: %s: %s\n", args[0], strerror(errno));
 		exit(errno == ENOENT ? 127 : 126);
@@ -356,6 +360,63 @@ parse_jobs(char *s)
 }
 
 int
+perc_match(char *pat, char *arg)
+{
+	char *s = strchr(pat, '%');
+
+	if (!s)
+		return strcmp(arg, pat) == 0;
+
+	size_t la = strlen(arg);
+	size_t lp = strlen(pat);
+
+	return la >= lp &&
+	    strncmp(arg, pat, s - pat) == 0 &&
+	    strncmp(arg + la - (pat + lp - (s + 1)),
+		s + 1,
+		pat + lp - (s + 1)) == 0;
+}
+
+char *
+perc_subst(char *pat, char *base, char *arg)
+{
+	static char buf[2048];
+	size_t l;
+	char *s = strchr(pat, '%');
+	char *t = strchr(arg, '%');
+
+	if (!t)
+		return arg;
+
+	if (s)
+		l = snprintf(buf, sizeof buf, "%.*s%.*s%.*s",
+		    (int)(t - arg),
+		    arg,
+				    
+		    (int)(strlen(base) - (pat + strlen(pat) - (s + 1))),
+		    base + (s - pat),
+				    
+		    (int)(arg + strlen(arg) - t),
+		    t+1);
+	else
+		l = snprintf(buf, sizeof buf, "%.*s%s%.*s",
+		    (int)(t - arg),
+		    arg,
+			    
+		    base,
+  
+		    (int)(arg + strlen(arg) - t),
+		    t+1);
+	
+	if (l >= sizeof buf) {
+		fprintf(stderr, "xe: result of percent subsitution too long\n");
+		exit(1);
+	}
+
+	return buf;
+}
+
+int
 main(int argc, char *argv[], char *envp[])
 {
 	int c, i, j, cmdend;
@@ -381,7 +442,7 @@ main(int argc, char *argv[], char *envp[])
 
 	traceout = stdout;
 
-	while ((c = getopt(argc, argv, "+0A:FI:LN:Raf:j:ns:v")) != -1)
+	while ((c = getopt(argc, argv, "+0A:FI:LN:Raf:j:nps:v")) != -1)
 		switch (c) {
 		case '0': delim = '\0'; break;
 		case 'A': argsep = optarg; Aflag++; break;
@@ -394,11 +455,12 @@ main(int argc, char *argv[], char *envp[])
 		case 'f': fflag = optarg; break;
 		case 'j': maxjobs = parse_jobs(optarg); break;
 		case 'n': nflag++; break;
+		case 'p': pflag++; break;
 		case 's': sflag = optarg; break;
 		case 'v': vflag++; traceout = stderr; break;
 		default:
 			fprintf(stderr,
-			    "Usage: %s [-0FLRnv] [-I arg] [-N maxargs] [-j maxjobs] COMMAND...\n"
+			    "Usage: %s [-0FLRnv] [-p | -I arg] [-N maxargs] [-j maxjobs] COMMAND...\n"
 			    "     | -f ARGFILE COMMAND...\n"
 			    "     | -s SHELLSCRIPT\n"
 			    "     | -a COMMAND... -- ARGS...\n"
@@ -455,6 +517,49 @@ main(int argc, char *argv[], char *envp[])
 		}
 	}
 
+	if (pflag) {
+		if (maxatonce != 1 || replace != default_replace) {
+			fprintf(stderr,
+			    "xe: -p cannot be used together with -N or -I.\n");
+			exit(1);
+		}
+
+		while ((arg = getarg())) {
+			buflen = 0;
+			argslen = 0;
+
+			int n;
+			for (n = optind, i = n + 1; n < cmdend; n = i + 1) {
+				char *pat = argv[n];
+
+				int matched = perc_match(pat, arg);
+
+				for (i = n + 1; i < cmdend; i++) {
+					if (argv[i][0] == '+' &&
+					    argv[i][1] == '\0')
+						break;
+
+					if (sflag) {
+						pusharg("/bin/sh");
+						pusharg("-c");
+						pusharg(sflag);
+						pusharg("-");
+					}
+
+					if (matched &&
+					    !pusharg(perc_subst(pat, arg, argv[i])))
+						toolong();
+				}
+
+				if (matched) {
+					run();
+					break;
+				}
+			}
+		}
+		// done with args, so default execution will do nothing
+	}
+
 	int keeparg = 0;
 	while (1) {
 		// check if there is an arg from a previous iteration