summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--smenu.132
-rw-r--r--smenu.c277
-rw-r--r--smenu.h4
3 files changed, 302 insertions, 11 deletions
diff --git a/smenu.1 b/smenu.1
index 3b596cf..b475b10 100644
--- a/smenu.1
+++ b/smenu.1
@@ -5,7 +5,8 @@ and outputs the selection to stdout.
.SH SYNOPSIS
smenu [\fB-h\fP] [\fB-n\fP \fIlines\fP] [\fB-t\fP [\fIcols\fP]]
[\fB-s\fP \fIpattern\fP] [\fB-m\fP \fImessage\fP] [\fB-w\fP] [\fB-d\fP]
-[\fB-c\fP] [\fB-r\fP] [\fB-b\fP] [\fB-i\fP regex] [\fB-e\fP regex]
+[\fB-c\fP] [\fB-r\fP] [\fB-b\fP] [\fB-i\fP regex]
+[\fB-e\fP regex] [\fB-I\fP /regex/string/[g]] [\fB-E\fP /regex/string/[g]]
[\fB-g\fP] [\fB-q\fP] [\fB-W\fP bytes] [\fB-L\fP bytes] [\fB-V\fP]
.SH DESCRIPTION
This small utility acts as a filter (reads from stdin and writes to
@@ -225,15 +226,12 @@ Sets the column mode. In this mode the lines of words do not wrap when
the right border of the terminal is reached but only when a special
character is read. Some words will not be displayed without an
horizontal scrolling.
-.RS
-.P
+
If such a scrolling is needed, some indications may appear on the left
and right edge of the window to help the user to reach the unseen words.
-.P
+
In this mode, the width of each column is minimal to keep the maximum
information visible on the terminal.
-.P
-.RE
.IP \fB-r\fP
Enables \fBENTER\fP to validate the selection even in search mode.
.IP \fB-b\fP
@@ -245,9 +243,25 @@ Sets the \fBe\fPxclude filter to match the non-selectable words.
This filter has a higher priority the include filter.
.P
-In the two previous options, \fIregex\fP is a \fBPOSIX\fP Extended
-Regular Expression. For details, refer to the manual page of the
-\fBegrep\fP command.
+The \fIregex\fP selections above are done before the possible word
+alterations realized with \fB-I\fP or \fB-E\fP (see below).
+.IP "\fB-I\fP /\fIregex\fP/replacement string/[g]"
+Post-processes the \fBincluded\fP (or \fBselectable\fP) words by doing
+a regular expression based substitution. The argument must be formatted
+as in the \fBsed\fP editor. The optional trailing \fBg\fP means that
+all matched occurences must be replaced and not the first one only.
+.IP "\fB-E\fP /\fIregex\fP/replacement string/[g]"
+Post-processes the \fBexcluded\fP (or \fBnon-selectable\fP) words by
+doing a regular expression based substitution (see \fB-I\fP for
+details).
+.P
+The \fB/\fP separator that \fB-I\fP and \fB-E\fP are using above can be
+substituted by any other character except \fISPACE\fP, \fI\\t\fP,
+\fI\\f\fP, \fI\\n\fP, \fI\\r\fP and \fI\\v\fP.
+.P
+In the four previous options, \fIregex\fP is a \fBPOSIX\fP
+\fBE\fPxtended \fBR\fPegular \fBE\fPxpression. For details, refer to
+the \fBregex\fP manual page.
.IP \fB-g\fP
Replaces the blank after each words in normal tabular mode by a vertical
bar "\fB|\fP". Some users may find the output more readable like that.
diff --git a/smenu.c b/smenu.c
index 6042a83..8074171 100644
--- a/smenu.c
+++ b/smenu.c
@@ -272,6 +272,10 @@ usage(char *prog)
"selectable words.\n");
fprintf(stderr, "-e sets the regex input filter to match the "
"non-selectable words.\n");
+ fprintf(stderr, "-I sets the post-processing action to apply to "
+ "selectable words.\n");
+ fprintf(stderr, "-E sets the post-processing action to apply to "
+ "non-selectable words.\n");
fprintf(stderr, "-g separates columns with '|' in tabulate mode.\n");
fprintf(stderr, "-q prevents the scrollbar display.\n");
fprintf(stderr, "-W sets the input words separators.\n");
@@ -938,6 +942,187 @@ get_cursor_position(int *const r, int *const c)
/* Strings and multibyte strings utility functions */
/* *********************************************** */
+/* ==================================================== */
+/* Parse the sed like sring passed as argument to -I/-E */
+/* ==================================================== */
+int
+parse_replacement_string(char *str, regex_t * re, char **rs, int *global)
+{
+ char sep;
+ char *first_sep_pos;
+ char *last_sep_pos;
+ char *buf;
+
+ if (strlen(str) < 4)
+ return 0;
+
+ /* Get the separator (the 1st character) */
+ /* """"""""""""""""""""""""""""""""""""" */
+ buf = strdup(str);
+ sep = buf[0];
+
+ /* Space like separators are not permited */
+ /* """""""""""""""""""""""""""""""""""""" */
+ if (isspace(sep))
+ goto err;
+
+ /* Get the extended regular expression */
+ /* """"""""""""""""""""""""""""""""""" */
+ if ((first_sep_pos = strchr(buf + 1, sep)) == NULL)
+ goto err;
+
+ *first_sep_pos = '\0';
+
+ /* Get the replacement string */
+ /* """""""""""""""""""""""""" */
+ if ((last_sep_pos = strchr(first_sep_pos + 1, sep)) == NULL)
+ goto err;
+
+ *last_sep_pos = '\0';
+
+ *rs = strdup(first_sep_pos + 1);
+
+ /* Get the global indicator (trailing g) */
+ /* """"""""""""""""""""""""""""""""""""" */
+ if (strcmp(last_sep_pos + 1, "g") == 0)
+ *global = 1;
+ else if (*(last_sep_pos + 1) == '\0')
+ *global = 0;
+ else
+ goto err;
+
+ /* Empty regular expression ? */
+ /* """""""""""""""""""""""""" */
+ if (*(buf + 1) == '\0')
+ goto err;
+
+ /* Compile the regular expression and abort on failure */
+ /* """"""""""""""""""""""""""""""""""""""""""""""""""" */
+ if (regcomp(re, buf + 1, REG_EXTENDED) != 0)
+ goto err;
+
+ free(buf);
+
+ return 1;
+
+err:
+ free(buf);
+
+ return 0;
+}
+
+/* ====================================================================== */
+/* Replace the part of a string matched by an extender regular expression */
+/* by the replacement string */
+/* The regex used must have been previously compiled */
+/* */
+/* orig: original string */
+/* subst: substitute containing \n's */
+/* re: regcomp compiled extended RE */
+/* global: 1 if all occurences must be replaced, else 0 */
+/* buf: destination buffer */
+/* bufsiz: destination buffer max size */
+/* ====================================================================== */
+int
+replace(char *orig, char *subst, regex_t * re, int global, char *buf,
+ size_t bufsiz)
+{
+ char *s;
+ regmatch_t match[10]; /* substrings from regexec */
+ size_t length = strlen(orig);
+ int j;
+
+ char *ptr_orig = orig;
+ char *ptr_subst;
+ int rc = 0;
+ int end_loop = 0;
+
+ /* Prepare to retry the replacement if global == 1 */
+ /* """"""""""""""""""""""""""""""""""""""""""""""" */
+ while (*ptr_orig && strlen(buf) < bufsiz)
+ {
+ s = buf + strlen(buf);
+ ptr_subst = subst;
+ match[0].rm_so = match[0].rm_eo = 0;
+
+ if (regexec(re, ptr_orig, 10, match, 0))
+ goto end;
+
+ for (j = 0; j < match[0].rm_so; j++)
+ *s++ = ptr_orig[j];
+
+ end_loop = 0;
+ for (; !end_loop && s < buf + bufsiz; s++)
+ {
+ switch (*s = *ptr_subst++)
+ {
+ case '\\':
+ switch (*s = *ptr_subst++)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ int i;
+ regmatch_t *m = &match[*s - '0'];
+
+ if ((i = m->rm_so) >= 0)
+ {
+ if ((size_t) m->rm_eo > length)
+ {
+ /* buggy GNU regexec!! */
+ /* =================== */
+ m->rm_eo = length;
+ }
+
+ while (i < m->rm_eo && s < buf + bufsiz)
+ *s++ = ptr_orig[i++];
+ }
+ s--;
+ break;
+ }
+
+ case '\0':
+ ptr_orig += match[0].rm_eo;
+ end_loop = 1;
+ rc = 1;
+ }
+ break;
+
+ case '\0':
+ /* end of the substitution string */
+ /* """""""""""""""""""""""""""""" */
+ ptr_orig += match[0].rm_eo;
+ end_loop = 1;
+ rc = 1;
+ }
+ }
+ if (!global)
+ goto end;
+
+ /* In case of empty string matching */
+ /* """""""""""""""""""""""""""""""" */
+ if (match[0].rm_eo == 0)
+ ptr_orig++;
+ }
+
+end:
+
+ if (end_loop)
+ strcat(buf, ptr_orig);
+ else
+ strcat(buf, ptr_orig + match[0].rm_eo);
+
+ return rc;
+}
+
/* ============================================================ */
/* Remove all ANSI color codes from s and puts the result in d. */
/* Memory space for d must have been allocated before. */
@@ -2622,9 +2807,17 @@ main(int argc, char *argv[])
* above the selection window */
char *include_pattern = ".";
char *exclude_pattern = NULL;
+ char *include_sed_pattern = NULL;
+ char *exclude_sed_pattern = NULL;
+ char *include_replace_str = NULL;
+ char *exclude_replace_str = NULL;
+ int include_global_replace;
+ int exclude_global_replace;
regex_t include_re;
regex_t exclude_re;
+ regex_t include_sed_re;
+ regex_t exclude_sed_re;
int message_lines;
@@ -2762,7 +2955,7 @@ main(int argc, char *argv[])
/* Command line options analysis */
/* """"""""""""""""""""""""""""" */
- while ((opt = egetopt(argc, argv, "Vhqdbi:e:cwrgn:t%m:s:W:L:")) != -1)
+ while ((opt = egetopt(argc, argv, "Vhqdbi:e:I:E:cwrgn:t%m:s:W:L:")) != -1)
{
switch (opt)
{
@@ -2856,6 +3049,28 @@ main(int argc, char *argv[])
}
break;
+ case 'I':
+ if (optarg && *optarg != '-')
+ include_sed_pattern = optarg;
+ else
+ {
+ fprintf(stderr, "Option requires an argument -- %c\n\n",
+ (char) optopt);
+ usage(argv[0]);
+ }
+ break;
+
+ case 'E':
+ if (optarg && *optarg != '-')
+ exclude_sed_pattern = optarg;
+ else
+ {
+ fprintf(stderr, "Option requires an argument -- %c\n\n",
+ (char) optopt);
+ usage(argv[0]);
+ }
+ break;
+
case 'q':
toggle.no_scrollbar = 1;
break;
@@ -3065,6 +3280,32 @@ main(int argc, char *argv[])
exit(EXIT_FAILURE);
}
+ /* Parse the post-processing patterns and extract its values */
+ /* """"""""""""""""""""""""""""""""""""""""""""""""""""""""" */
+ if (include_sed_pattern)
+ if (parse_replacement_string(include_sed_pattern,
+ &include_sed_re,
+ &include_replace_str,
+ &include_global_replace) == 0)
+ {
+ fprintf(stderr, "Bad -I argument. Must be something like: "
+ "/regex/repl_string/[g]\n");
+
+ exit(EXIT_FAILURE);
+ }
+
+ if (exclude_sed_pattern)
+ if (parse_replacement_string(exclude_sed_pattern,
+ &exclude_sed_re,
+ &exclude_replace_str,
+ &exclude_global_replace) == 0)
+ {
+ fprintf(stderr, "Bad -E argument. Must be something like: "
+ "/regex/repl_string/[g]\n");
+
+ exit(EXIT_FAILURE);
+ }
+
/* Get and process the input stream words */
/* """""""""""""""""""""""""""""""""""""" */
while ((word = get_word(stdin, word_delims_list, record_delims_list,
@@ -3076,11 +3317,12 @@ main(int argc, char *argv[])
wchar_t *tmpw;
int s;
ll_t *list = NULL;
- char *dest = xmalloc(5 * strlen(word) + 1);
+ char *dest;
char *new_dest;
size_t len;
size_t word_len;
int selectable;
+ char buf[1024] = { 0 };
/* Check if the word will be selectable or not */
/* """"""""""""""""""""""""""""""""""""""""""" */
@@ -3095,9 +3337,40 @@ main(int argc, char *argv[])
else
selectable = 0;
+ /* Possibly modify the word acording to -I/-E arguments */
+ /* """""""""""""""""""""""""""""""""""""""""""""""""""" */
+ if (selectable && include_sed_pattern)
+ {
+ if (replace(word, include_replace_str, &include_sed_re,
+ include_global_replace, buf, 1024))
+ {
+ free(word);
+
+ if (*buf == '\0')
+ word = strdup("_");
+ else
+ word = strdup(buf);
+ }
+ }
+
+ if (!selectable && exclude_sed_pattern)
+ {
+ if (replace(word, exclude_replace_str, &exclude_sed_re,
+ exclude_global_replace, buf, 1024))
+ {
+ free(word);
+
+ if (*buf == '\0')
+ word = strdup("_");
+ else
+ word = strdup(buf);
+ }
+ }
+
/* Alter the word just read be replacing special chars by their */
/* escaped equivalents. */
/* """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" */
+ dest = xmalloc(5 * strlen(word) + 1);
len = expand(word, dest, &langinfo);
new_dest = strdup(dest);
free(dest);
diff --git a/smenu.h b/smenu.h
index c946fe8..c70bf9e 100644
--- a/smenu.h
+++ b/smenu.h
@@ -101,3 +101,7 @@ void set_new_first_column(win_t * win, word_t * word_a);
void sig_handler(int s);
void set_new_first_column(win_t * win, word_t * word_a);
+
+int parse_replacement_string(char *str, regex_t * re, char **rs, int *global);
+int replace(char *orig, char *subst, regex_t * re, int global, char *buf,
+ size_t bufsiz);