commit-mailutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[SCM] GNU Mailutils branch, master, updated. release-2.2-732-gf160ca7


From: Sergey Poznyakoff
Subject: [SCM] GNU Mailutils branch, master, updated. release-2.2-732-gf160ca7
Date: Wed, 16 Sep 2015 05:51:00 +0000

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GNU Mailutils".

http://git.savannah.gnu.org/cgit/mailutils.git/commit/?id=f160ca75991d5bf994afe3cb5bc549b113b14bbd

The branch, master has been updated
       via  f160ca75991d5bf994afe3cb5bc549b113b14bbd (commit)
      from  cd2126bee8534176c4e28dcf8f326f19d3132279 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit f160ca75991d5bf994afe3cb5bc549b113b14bbd
Author: Sergey Poznyakoff <address@hidden>
Date:   Wed Sep 16 08:39:51 2015 +0300

    Port wordsplit from grecs 20616b88
    
    The updated version supports tilde and pathname expansion, command
    substitution and standard shell-like replacement constructs in
    variable substitution, such as ${X:-V} etc.
    
    * include/mailutils/wordsplit.h: Update.
    * libmailutils/string/wordsplit.c: Update.
    * libmailutils/tests/wsp.c: Update.
    * libmailutils/tests/wordsplit.at: Update unescape test.
    
    * libmailutils/imapio/create.c (mu_imapio_create): Initialize ws_escape
    array.
    * libmailutils/mime/mimehdr.c (_mime_header_parse): Likewise.
    * libmailutils/tests/modmesg.c: Use mu_wordsplit with MU_WRDSF_NOSPLIT
    to run expansions on the string.
    * mu/shell.c (shell_prompt): Likewise.

-----------------------------------------------------------------------

Summary of changes:
 include/mailutils/wordsplit.h   |  196 ++++--
 libmailutils/imapio/create.c    |    8 +-
 libmailutils/mime/mimehdr.c     |    6 +-
 libmailutils/string/wordsplit.c | 1374 +++++++++++++++++++++++++++++----------
 libmailutils/tests/modmesg.c    |   20 +-
 libmailutils/tests/wordsplit.at |    2 +-
 libmailutils/tests/wsp.c        |  346 +++++++++--
 mu/shell.c                      |   16 +-
 8 files changed, 1514 insertions(+), 454 deletions(-)

diff --git a/include/mailutils/wordsplit.h b/include/mailutils/wordsplit.h
index 5da3803..aaf4bd7 100644
--- a/include/mailutils/wordsplit.h
+++ b/include/mailutils/wordsplit.h
@@ -1,5 +1,5 @@
 /* wordsplit - a word splitter
-   Copyright (C) 2009, 2010 Sergey Poznyakoff
+   Copyright (C) 2009-2015 Sergey Poznyakoff
 
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the
@@ -23,42 +23,99 @@
 extern "C" {
 #endif
 
-struct mu_wordsplit
+typedef struct mu_wordsplit mu_wordsplit_t;
+
+/* Structure used to direct the splitting.  Members marked with [Input]
+   can be defined before calling mu_wordsplit(), those marked with [Output]
+   provide return values when the function returns.  If neither mark is
+   used, the member is internal and must not be used by the caller.
+
+   In the comments below, the
+   identifiers in parentheses indicate bits that must be set (or unset, if
+   starting with !) in the ws_flags to initialize or use the given member.
+   If not redefined explicitly, most of them are set to some reasonable
+   default value upon entry to mu_wordsplit(). */
+struct mu_wordsplit            
 {
-  size_t ws_wordc;
-  char **ws_wordv;
-  size_t ws_offs;
-  size_t ws_wordn;
-  int ws_flags;
-  const char *ws_delim;
-  const char *ws_comment;
-  const char *ws_escape;
-  void (*ws_alloc_die) (struct mu_wordsplit *wsp);
+  size_t ws_wordc;          /* [Output] Number of words in ws_wordv. */
+  char **ws_wordv;          /* [Output] Array of parsed out words. */
+  size_t ws_offs;           /* [Input] (MU_WRDSF_DOOFFS) Number of initial
+                              elements in ws_wordv to fill with NULLs. */
+  size_t ws_wordn;          /* Number of elements ws_wordv can accomodate. */ 
+  int ws_flags;             /* [Input] Flags passed to mu_wordsplit. */
+  int ws_options;           /* [Input] (MU_WRDSF_PATHEXPAND)
+                              Additional options. */
+  const char *ws_delim;     /* [Input] (MU_WRDSF_DELIM) Word delimiters. */
+  const char *ws_comment;   /* [Input] (MU_WRDSF_COMMENT) Comment characters. 
*/
+  const char *ws_escape[2]; /* [Input] (MU_WRDSF_ESCAPE) Characters to be 
escaped
+                              with backslash. */
+  void (*ws_alloc_die) (mu_wordsplit_t *wsp);
+                            /* [Input] (MU_WRDSF_ALLOC_DIE) Function called 
when
+                              out of memory.  Must not return. */
   void (*ws_error) (const char *, ...)
-                   __attribute__ ((__format__ (__printf__, 1, 2)));
+                   __attribute__ ((__format__ (__printf__, 1, 2)));
+                            /* [Input] (MU_WRDSF_ERROR) Function used for error
+                              reporting */
   void (*ws_debug) (const char *, ...)
-                   __attribute__ ((__format__ (__printf__, 1, 2)));
-       
-  const char **ws_env;
-  const char *(*ws_getvar) (const char *, size_t, void *); 
-  void *ws_closure;
+                   __attribute__ ((__format__ (__printf__, 1, 2)));
+                            /* [Input] (MU_WRDSF_DEBUG) Function used for debug
+                              output. */
+  const char **ws_env;      /* [Input] (MU_WRDSF_ENV, !MU_WRDSF_NOVAR) Array of
+                              environment variables. */
+
+  char **ws_envbuf;
+  size_t ws_envidx;
+  size_t ws_envsiz;
   
-  const char *ws_input;
-  size_t ws_len;
-  size_t ws_endp;
-  int ws_errno;
+  int (*ws_getvar) (char **ret, const char *var, size_t len, void *clos);
+                            /* [Input] (MU_WRDSF_GETVAR, !MU_WRDSF_NOVAR) 
Looks up
+                              the name VAR (LEN bytes long) in the table of
+                              variables and if found returns in memory
+                              location pointed to by RET the value of that
+                              variable.  Returns WRDSE_OK (0) on success,
+                              and an error code (see WRDSE_* defines below)
+                              on error.  User-specific errors can be returned
+                              by storing the error diagnostic string in RET
+                              and returning WRDSE_USERERR.
+                               Whatever is stored in RET, it must be allocated
+                              using malloc(3). */
+  void *ws_closure;         /* [Input] (MU_WRDSF_CLOSURE) Passed as the CLOS
+                              argument to ws_getvar and ws_command. */
+  int (*ws_command) (char **ret, const char *cmd, size_t len, char **argv,
+                     void *clos);
+                           /* [Input] (!MU_WRDSF_NOCMD) Returns in the memory
+                              location pointed to by RET the expansion of
+                              the command CMD (LEN bytes nong).  If 
MU_WRDSF_ARGV
+                              flag is set, ARGV contains CMD split out to
+                              words.  Otherwise ARGV is NULL.
+
+                              See ws_getvar for a discussion of possible
+                              return values. */
+       
+  const char *ws_input;     /* Input string (the S argument to mu_wordsplit. 
*/  
+  size_t ws_len;            /* Length of ws_input. */
+  size_t ws_endp;           /* Points past the last processed byte in
+                              ws_input. */
+  int ws_errno;             /* [Output] Error code, if an error occurred. */
+  char *ws_usererr;         /* Points to textual description of
+                              the error, if ws_errno is WRDSE_USERERR.  Must
+                              be allocated with malloc(3). */
   struct mu_wordsplit_node *ws_head, *ws_tail;
+                            /* Doubly-linked list of parsed out nodes. */
+  int ws_lvl;               /* Invocation nesting level. */
 };
 
-/* Wordsplit flags.  Only 2 bits of a 32-bit word remain unused.
-   It is getting crowded... */
+/* Initial size for ws_env, if allocated automatically */
+#define MU_WORDSPLIT_ENV_INIT 16
+
+/* Mu_Wordsplit flags. */
 /* Append the words found to the array resulting from a previous
    call. */
 #define MU_WRDSF_APPEND            0x00000001
-/* Insert we_offs initial NULLs in the array ws_wordv.
+/* Insert ws_offs initial NULLs in the array ws_wordv.
    (These are not counted in the returned ws_wordc.) */
 #define MU_WRDSF_DOOFFS            0x00000002
-/* Don't do command substitution. Reserved for future use. */
+/* Don't do command substitution. */
 #define MU_WRDSF_NOCMD             0x00000004
 /* The parameter p resulted from a previous call to
    mu_wordsplit(), and mu_wordsplit_free() was not called. Reuse the
@@ -66,10 +123,8 @@ struct mu_wordsplit
 #define MU_WRDSF_REUSE             0x00000008
 /* Print errors */
 #define MU_WRDSF_SHOWERR           0x00000010
-/* Consider it an error if an undefined shell variable
-   is expanded. */
+/* Consider it an error if an undefined variable is expanded. */
 #define MU_WRDSF_UNDEF             0x00000020
-
 /* Don't do variable expansion. */
 #define MU_WRDSF_NOVAR             0x00000040
 /* Abort on ENOMEM error */
@@ -80,7 +135,7 @@ struct mu_wordsplit
 #define MU_WRDSF_SQUOTE            0x00000200
 /* Handle double quotes */
 #define MU_WRDSF_DQUOTE            0x00000400
-/* Handle quotes and escape directives */
+/* Handle single and double quotes */
 #define MU_WRDSF_QUOTE             (MU_WRDSF_SQUOTE|MU_WRDSF_DQUOTE)
 /* Replace each input sequence of repeated delimiters with a single
    delimiter */
@@ -108,59 +163,92 @@ struct mu_wordsplit
 /* Don't split input into words.  Useful for side effects. */
 #define MU_WRDSF_NOSPLIT           0x00400000
 /* Keep undefined variables in place, instead of expanding them to
-   empty string */
+   empty strings. */
 #define MU_WRDSF_KEEPUNDEF         0x00800000
 /* Warn about undefined variables */
 #define MU_WRDSF_WARNUNDEF         0x01000000
 /* Handle C escapes */
 #define MU_WRDSF_CESCAPES          0x02000000
-
 /* ws_closure is set */
 #define MU_WRDSF_CLOSURE           0x04000000
 /* ws_env is a Key/Value environment, i.e. the value of a variable is
    stored in the element that follows its name. */
 #define MU_WRDSF_ENV_KV            0x08000000
-
 /* ws_escape is set */
 #define MU_WRDSF_ESCAPE            0x10000000
-
 /* Incremental mode */
 #define MU_WRDSF_INCREMENTAL       0x20000000
+/* Perform pathname and tilde expansion */
+#define MU_WRDSF_PATHEXPAND        0x40000000
+/* ws_options is initialized */
+#define MU_WRDSF_OPTIONS           0x80000000
 
 #define MU_WRDSF_DEFFLAGS             \
   (MU_WRDSF_NOVAR | MU_WRDSF_NOCMD | \
    MU_WRDSF_QUOTE | MU_WRDSF_SQUEEZE_DELIMS | MU_WRDSF_CESCAPES)
 
-#define MU_WRDSE_EOF        0
+/* Remove the word that produces empty string after path expansion */
+#define MU_WRDSO_NULLGLOB        0x00000001
+/* Print error message if path expansion produces empty string */
+#define MU_WRDSO_FAILGLOB        0x00000002
+/* Allow a leading period to be matched by metacharacters. */
+#define MU_WRDSO_DOTGLOB         0x00000004
+/* ws_command needs argv parameter */
+#define MU_WRDSO_ARGV            0x00000008
+/* Keep backslash in unrecognized escape sequences in words */
+#define MU_WRDSO_BSKEEP_WORD     0x00000010
+/* Handle octal escapes in words */
+#define MU_WRDSO_OESC_WORD       0x00000020
+/* Handle hex escapes in words */
+#define MU_WRDSO_XESC_WORD       0x00000040
+
+/* Keep backslash in unrecognized escape sequences in quoted strings */
+#define MU_WRDSO_BSKEEP_QUOTE    0x00000100
+/* Handle octal escapes in quoted strings */
+#define MU_WRDSO_OESC_QUOTE      0x00000200
+/* Handle hex escapes in quoted strings */
+#define MU_WRDSO_XESC_QUOTE      0x00000400
+
+#define MU_WRDSO_BSKEEP          MU_WRDSO_BSKEEP_WORD     
+#define MU_WRDSO_OESC            MU_WRDSO_OESC_WORD       
+#define MU_WRDSO_XESC            MU_WRDSO_XESC_WORD       
+
+/* Set escape option F in WS for words (Q==0) or quoted strings (Q==1) */
+#define MU_WRDSO_ESC_SET(ws,q,f) ((ws)->ws_options |= ((f) << 4*(q)))
+/* Test WS for escape option F for words (Q==0) or quoted strings (Q==1) */
+#define MU_WRDSO_ESC_TEST(ws,q,f) ((ws)->ws_options & ((f) << 4*(q)))
+
+#define MU_WRDSE_OK         0
+#define MU_WRDSE_EOF        MU_WRDSE_OK
 #define MU_WRDSE_QUOTE      1
 #define MU_WRDSE_NOSPACE    2
-#define MU_WRDSE_NOSUPP     3
-#define MU_WRDSE_USAGE      4
-#define MU_WRDSE_CBRACE     5
-#define MU_WRDSE_UNDEF      6
-#define MU_WRDSE_NOINPUT    7
-
-int mu_wordsplit (const char *s, struct mu_wordsplit *p, int flags);
-int mu_wordsplit_len (const char *s, size_t len,
-                     struct mu_wordsplit *p, int flags);
-void mu_wordsplit_free (struct mu_wordsplit *p);
-void mu_wordsplit_free_words (struct mu_wordsplit *ws);
+#define MU_WRDSE_USAGE      3
+#define MU_WRDSE_CBRACE     4
+#define MU_WRDSE_UNDEF      5
+#define MU_WRDSE_NOINPUT    6
+#define MU_WRDSE_PAREN      7
+#define MU_WRDSE_GLOBERR    8
+#define MU_WRDSE_USERERR    9
+
+int mu_wordsplit (const char *s, mu_wordsplit_t *ws, int flags);
+int mu_wordsplit_len (const char *s, size_t len, mu_wordsplit_t *ws, int 
flags);
+void mu_wordsplit_free (mu_wordsplit_t *ws);
+void mu_wordsplit_free_words (mu_wordsplit_t *ws);
+void mu_wordsplit_free_envbuf (mu_wordsplit_t *ws);
+void mu_wordsplit_getwords (mu_wordsplit_t *ws, int *wordc, char ***wordv);
 
 int mu_wordsplit_c_unquote_char (int c);
 int mu_wordsplit_c_quote_char (int c);
-size_t mu_wordsplit_c_quoted_length (const char *str, int quote_hex,
-                                    int *quote);
-void mu_wordsplit_general_unquote_copy (char *dst, const char *src, size_t n,
-                                       const char *escapable);
-void mu_wordsplit_sh_unquote_copy (char *dst, const char *src, size_t n);
-void mu_wordsplit_c_unquote_copy (char *dst, const char *src, size_t n);
+size_t mu_wordsplit_c_quoted_length (const char *str, int quote_hex, int 
*quote);
 void mu_wordsplit_c_quote_copy (char *dst, const char *src, int quote_hex);
 
-void mu_wordsplit_perror (struct mu_wordsplit *ws);
-const char *mu_wordsplit_strerror (struct mu_wordsplit *ws);
+void mu_wordsplit_perror (mu_wordsplit_t *ws);
+const char *mu_wordsplit_strerror (mu_wordsplit_t *ws);
+
+void mu_wordsplit_clearerr (mu_wordsplit_t *ws);
 
 #ifdef __cplusplus
 }
 #endif
-
+  
 #endif
diff --git a/libmailutils/imapio/create.c b/libmailutils/imapio/create.c
index e33d551..9d83c4d 100644
--- a/libmailutils/imapio/create.c
+++ b/libmailutils/imapio/create.c
@@ -29,7 +29,10 @@ mu_imapio_create (mu_imapio_t *iop, mu_stream_t str, int 
server)
   io->_imap_stream = str;
   mu_stream_ref (str);
   io->_imap_ws.ws_delim = " \t()[]";
-  io->_imap_ws.ws_escape = "\\\"";
+  io->_imap_ws.ws_escape[0] = NULL;
+  io->_imap_ws.ws_escape[1] = "\\\\\"\"";
+  MU_WRDSO_ESC_SET (&io->_imap_ws, 0, MU_WRDSO_BSKEEP);
+  MU_WRDSO_ESC_SET (&io->_imap_ws, 1, MU_WRDSO_BSKEEP);
   io->_imap_ws_flags = MU_WRDSF_DELIM |
                        MU_WRDSF_ESCAPE |
                        MU_WRDSF_NOVAR |
@@ -37,7 +40,8 @@ mu_imapio_create (mu_imapio_t *iop, mu_stream_t str, int 
server)
                        MU_WRDSF_DQUOTE |
                        MU_WRDSF_RETURN_DELIMS |
                        MU_WRDSF_WS |
-                       MU_WRDSF_APPEND;
+                       MU_WRDSF_APPEND |
+                       MU_WRDSF_OPTIONS;
   io->_imap_server = server;
   *iop = io;
   return 0;
diff --git a/libmailutils/mime/mimehdr.c b/libmailutils/mime/mimehdr.c
index 5a2df40..e1f24b5 100644
--- a/libmailutils/mime/mimehdr.c
+++ b/libmailutils/mime/mimehdr.c
@@ -293,12 +293,14 @@ _mime_header_parse (const char *text, char **pvalue,
   size_t i;
 
   ws.ws_delim = " \t\r\n;";
-  ws.ws_escape = "\\\"";
+  ws.ws_escape[0] = ws.ws_escape[1] = "\\\\\"\"";
+  MU_WRDSO_ESC_SET (&ws, 0, MU_WRDSO_BSKEEP);
+  MU_WRDSO_ESC_SET (&ws, 1, MU_WRDSO_BSKEEP);
   if (mu_wordsplit (text, &ws,
                    MU_WRDSF_DELIM | MU_WRDSF_ESCAPE |
                    MU_WRDSF_NOVAR | MU_WRDSF_NOCMD |
                    MU_WRDSF_DQUOTE | MU_WRDSF_SQUEEZE_DELIMS |
-                   MU_WRDSF_RETURN_DELIMS | MU_WRDSF_WS))
+                   MU_WRDSF_RETURN_DELIMS | MU_WRDSF_WS | MU_WRDSF_OPTIONS))
     {
       mu_debug (MU_DEBCAT_MIME, MU_DEBUG_ERROR,
                (_("wordsplit: %s"), mu_wordsplit_strerror (&ws)));
diff --git a/libmailutils/string/wordsplit.c b/libmailutils/string/wordsplit.c
index 0acf2a3..755f5d4 100644
--- a/libmailutils/string/wordsplit.c
+++ b/libmailutils/string/wordsplit.c
@@ -1,5 +1,5 @@
 /* wordsplit - a word splitter
-   Copyright (C) 2009, 2010 Sergey Poznyakoff
+   Copyright (C) 2009-2015 Sergey Poznyakoff
 
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the
@@ -31,6 +31,8 @@
 #include <string.h>
 #include <stdio.h>
 #include <stdarg.h>
+#include <pwd.h>
+#include <glob.h>
 
 #include <mailutils/nls.h>
 #include <mailutils/wordsplit.h>
@@ -47,6 +49,9 @@
 #define ISALNUM(c) (ISALPHA(c) || ISDIGIT(c))
 #define ISPRINT(c) (' ' <= ((unsigned) (c)) && ((unsigned) (c)) <= 127)
 
+#define ISVARBEG(c) (ISALPHA(c) || c == '_')
+#define ISVARCHR(c) (ISALNUM(c) || c == '_')
+       
 #define ALLOC_INIT 128
 #define ALLOC_INCR 128
 
@@ -71,6 +76,15 @@ _wsplt_error (const char *fmt, ...)
 static void mu_wordsplit_free_nodes (struct mu_wordsplit *);
 
 static int
+_wsplt_seterr (struct mu_wordsplit *wsp, int ec)
+{
+  wsp->ws_errno = ec;
+  if (wsp->ws_flags & MU_WRDSF_SHOWERR)
+    mu_wordsplit_perror (wsp);
+  return ec;
+}
+  
+static int
 _wsplt_nomem (struct mu_wordsplit *wsp)
 {
   errno = ENOMEM;
@@ -85,6 +99,62 @@ _wsplt_nomem (struct mu_wordsplit *wsp)
   return wsp->ws_errno;
 }
 
+static int mu_wordsplit_run (const char *command, size_t length,
+                         struct mu_wordsplit *wsp,
+                         int flags, int lvl);
+
+static int
+_wsplt_subsplit (struct mu_wordsplit *wsp, struct mu_wordsplit *wss,
+                char const *str, int len,
+                int flags)
+{
+  wss->ws_delim = wsp->ws_delim;
+  wss->ws_debug = wsp->ws_debug;
+  wss->ws_error = wsp->ws_error;
+  wss->ws_alloc_die = wsp->ws_alloc_die;
+
+  if (!(flags & MU_WRDSF_NOVAR))
+    {
+      wss->ws_env = wsp->ws_env;
+      wss->ws_getvar = wsp->ws_getvar;
+      flags |= wsp->ws_flags & (MU_WRDSF_ENV | MU_WRDSF_ENV_KV | 
MU_WRDSF_GETVAR);
+    }
+  if (!(flags & MU_WRDSF_NOCMD))
+    {
+      wss->ws_command = wsp->ws_command;
+    }
+
+  if ((flags & (MU_WRDSF_NOVAR|MU_WRDSF_NOCMD)) != 
(MU_WRDSF_NOVAR|MU_WRDSF_NOCMD))
+    {
+      wss->ws_closure = wsp->ws_closure;
+      flags |= wsp->ws_flags & MU_WRDSF_CLOSURE;
+    }
+
+  wss->ws_options = wsp->ws_options;
+  
+  flags |= MU_WRDSF_DELIM
+         | MU_WRDSF_ALLOC_DIE
+         | MU_WRDSF_ERROR
+         | MU_WRDSF_DEBUG
+         | (wsp->ws_flags & (MU_WRDSF_SHOWDBG | MU_WRDSF_SHOWERR | 
MU_WRDSF_OPTIONS));
+           
+  return mu_wordsplit_run (str, len, wss, flags, wsp->ws_lvl + 1);
+}
+
+static void
+_wsplt_seterr_sub (struct mu_wordsplit *wsp, struct mu_wordsplit *wss)
+{
+  if (wsp->ws_errno == MU_WRDSE_USERERR)
+    free (wsp->ws_usererr);
+  wsp->ws_errno = wss->ws_errno;
+  if (wss->ws_errno == MU_WRDSE_USERERR)
+    {
+      wsp->ws_usererr = wss->ws_usererr;
+      wss->ws_errno = MU_WRDSE_EOF;
+      wss->ws_usererr = NULL;
+    }
+}
+
 static void
 mu_wordsplit_init0 (struct mu_wordsplit *wsp)
 {
@@ -92,6 +162,7 @@ mu_wordsplit_init0 (struct mu_wordsplit *wsp)
     {
       if (!(wsp->ws_flags & MU_WRDSF_APPEND))
        mu_wordsplit_free_words (wsp);
+      mu_wordsplit_clearerr (wsp);
     }
   else
     {
@@ -102,11 +173,13 @@ mu_wordsplit_init0 (struct mu_wordsplit *wsp)
 
   wsp->ws_errno = 0;
   wsp->ws_head = wsp->ws_tail = NULL;
-}  
+}
 
+char mu_wordsplit_c_escape_tab[] = "\\\\\"\"a\ab\bf\fn\nr\rt\tv\v";
+  
 static int
 mu_wordsplit_init (struct mu_wordsplit *wsp, const char *input, size_t len,
-                  int flags)
+               int flags)
 {
   wsp->ws_flags = flags;
 
@@ -115,23 +188,21 @@ mu_wordsplit_init (struct mu_wordsplit *wsp, const char 
*input, size_t len,
   if (!(wsp->ws_flags & MU_WRDSF_ERROR))
     wsp->ws_error = _wsplt_error;
 
-  if (!(wsp->ws_flags & MU_WRDSF_NOVAR)
-      && !(wsp->ws_flags & (MU_WRDSF_ENV | MU_WRDSF_GETVAR)))
+  if (!(wsp->ws_flags & MU_WRDSF_NOVAR))
     {
-      errno = EINVAL;
-      wsp->ws_errno = MU_WRDSE_USAGE;
-      if (wsp->ws_flags & MU_WRDSF_SHOWERR)
-       mu_wordsplit_perror (wsp);
-      return wsp->ws_errno;
+      /* These will be initialized on first variable assignment */
+      wsp->ws_envidx = wsp->ws_envsiz = 0;
+      wsp->ws_envbuf = NULL;
     }
 
   if (!(wsp->ws_flags & MU_WRDSF_NOCMD))
     {
-      errno = EINVAL;
-      wsp->ws_errno = MU_WRDSE_NOSUPP;
-      if (wsp->ws_flags & MU_WRDSF_SHOWERR)
-       mu_wordsplit_perror (wsp);
-      return wsp->ws_errno;
+      if (!wsp->ws_command)
+       {
+         _wsplt_seterr (wsp, MU_WRDSE_USAGE);
+         errno = EINVAL;
+         return wsp->ws_errno;
+       }
     }
 
   if (wsp->ws_flags & MU_WRDSF_SHOWDBG)
@@ -162,8 +233,35 @@ mu_wordsplit_init (struct mu_wordsplit *wsp, const char 
*input, size_t len,
   if (!(wsp->ws_flags & MU_WRDSF_CLOSURE))
     wsp->ws_closure = NULL;
 
-  wsp->ws_endp = 0;
+  if (!(wsp->ws_flags & MU_WRDSF_OPTIONS))
+    wsp->ws_options = 0;
+
+  if (wsp->ws_flags & MU_WRDSF_ESCAPE)
+    {
+      if (!wsp->ws_escape[0])
+       wsp->ws_escape[0] = "";
+      if (!wsp->ws_escape[1])
+       wsp->ws_escape[1] = "";
+    }
+  else
+    {
+      if (wsp->ws_flags & MU_WRDSF_CESCAPES)
+       {
+         wsp->ws_escape[0] = mu_wordsplit_c_escape_tab;
+         wsp->ws_escape[1] = mu_wordsplit_c_escape_tab;
+         wsp->ws_options |= MU_WRDSO_OESC_QUOTE | MU_WRDSO_OESC_WORD       
+                            | MU_WRDSO_XESC_QUOTE | MU_WRDSO_XESC_WORD;
+       }
+      else
+       {
+         wsp->ws_escape[0] = "";
+         wsp->ws_escape[1] = "\\\\\"\"";
+         wsp->ws_options |= MU_WRDSO_BSKEEP_QUOTE;
+       }
+    }
   
+  wsp->ws_endp = 0;
+
   mu_wordsplit_init0 (wsp);
 
   return 0;
@@ -202,14 +300,15 @@ alloc_space (struct mu_wordsplit *wsp, size_t count)
 
 
 /* Node state flags */
-#define _WSNF_NULL     0x01     /* null node (a noop) */
+#define _WSNF_NULL     0x01    /* null node (a noop) */
 #define _WSNF_WORD     0x02    /* node contains word in v.word */
 #define _WSNF_QUOTE    0x04    /* text is quoted */
 #define _WSNF_NOEXPAND 0x08    /* text is not subject to expansion */
 #define _WSNF_JOIN     0x10    /* node must be joined with the next node */
 #define _WSNF_SEXP     0x20    /* is a sed expression */
+#define _WSNF_DELIM    0x40     /* node is a delimiter */
 
-#define _WSNF_EMPTYOK  0x0100   /* special flag indicating that
+#define _WSNF_EMPTYOK  0x0100  /* special flag indicating that
                                   mu_wordsplit_add_segm must add the
                                   segment even if it is empty */
 
@@ -232,7 +331,7 @@ struct mu_wordsplit_node
 static const char *
 wsnode_flagstr (int flags)
 {
-  static char retbuf[6];
+  static char retbuf[7];
   char *p = retbuf;
 
   if (flags & _WSNF_WORD)
@@ -257,6 +356,10 @@ wsnode_flagstr (int flags)
     *p++ = 's';
   else
     *p++ = '-';
+  if (flags & _WSNF_DELIM)
+    *p++ = 'd';
+  else
+    *p++ = '-';
   *p = 0;
   return retbuf;
 }
@@ -374,8 +477,7 @@ wsnode_insert (struct mu_wordsplit *wsp, struct 
mu_wordsplit_node *node,
 }
 
 static int
-mu_wordsplit_add_segm (struct mu_wordsplit *wsp, size_t beg, size_t end,
-                      int flg)
+mu_wordsplit_add_segm (struct mu_wordsplit *wsp, size_t beg, size_t end, int 
flg)
 {
   struct mu_wordsplit_node *node;
   int rc;
@@ -385,7 +487,7 @@ mu_wordsplit_add_segm (struct mu_wordsplit *wsp, size_t 
beg, size_t end,
   rc = wsnode_new (wsp, &node);
   if (rc)
     return rc;
-  node->flags = flg & ~(_WSNF_WORD|_WSNF_EMPTYOK);
+  node->flags = flg & ~(_WSNF_WORD | _WSNF_EMPTYOK);
   node->v.segm.beg = beg;
   node->v.segm.end = end;
   wsnode_append (wsp, node);
@@ -415,12 +517,14 @@ mu_wordsplit_dump_nodes (struct mu_wordsplit *wsp)
   for (p = wsp->ws_head, n = 0; p; p = p->next, n++)
     {
       if (p->flags & _WSNF_WORD)
-       wsp->ws_debug ("%4d: %p: %#04x (%s):%s;",
+       wsp->ws_debug ("(%02d) %4d: %p: %#04x (%s):%s;",
+                      wsp->ws_lvl,
                       n, p, p->flags, wsnode_flagstr (p->flags), p->v.word);
       else
-       wsp->ws_debug ("%4d: %p: %#04x (%s):%.*s;",
+       wsp->ws_debug ("(%02d) %4d: %p: %#04x (%s):%.*s;",
+                      wsp->ws_lvl,
                       n, p, p->flags, wsnode_flagstr (p->flags),
-                      (int)(p->v.segm.end - p->v.segm.beg),
+                      (int) (p->v.segm.end - p->v.segm.beg),
                       wsp->ws_input + p->v.segm.beg);
     }
 }
@@ -437,7 +541,8 @@ coalesce_segment (struct mu_wordsplit *wsp, struct 
mu_wordsplit_node *node)
     {
       len += wsnode_len (p);
     }
-  len += wsnode_len (p);
+  if (p)
+    len += wsnode_len (p);
   end = p;
 
   buf = malloc (len + 1);
@@ -456,6 +561,7 @@ coalesce_segment (struct mu_wordsplit *wsp, struct 
mu_wordsplit_node *node)
       cur += slen;
       if (p != node)
        {
+         node->flags |= p->flags & _WSNF_QUOTE;
          wsnode_remove (wsp, p);
          stop = p == end;
          wsnode_free (p);
@@ -475,13 +581,15 @@ coalesce_segment (struct mu_wordsplit *wsp, struct 
mu_wordsplit_node *node)
   return 0;
 }
 
+static void mu_wordsplit_string_unquote_copy (struct mu_wordsplit *ws,
+                                             int inquote,
+                                             char *dst, const char *src,
+                                             size_t n);
+
 static int
 wsnode_quoteremoval (struct mu_wordsplit *wsp)
 {
   struct mu_wordsplit_node *p;
-  void (*uqfn) (char *, const char *, size_t) =
-    (wsp->ws_flags & MU_WRDSF_CESCAPES) ?
-    mu_wordsplit_c_unquote_copy : mu_wordsplit_sh_unquote_copy;
 
   for (p = wsp->ws_head; p; p = p->next)
     {
@@ -509,11 +617,8 @@ wsnode_quoteremoval (struct mu_wordsplit *wsp)
              p->flags |= _WSNF_WORD;
            }
 
-         if (wsp->ws_flags & MU_WRDSF_ESCAPE)
-           mu_wordsplit_general_unquote_copy (p->v.word, str, slen,
-                                              wsp->ws_escape);
-         else
-           uqfn (p->v.word, str, slen);
+         mu_wordsplit_string_unquote_copy (wsp, p->flags & _WSNF_QUOTE,
+                                           p->v.word, str, slen);
        }
     }
   return 0;
@@ -606,10 +711,10 @@ node_split_prefix (struct mu_wordsplit *wsp,
 }
 
 static int
-find_closing_cbrace (const char *str, size_t i, size_t len, size_t * poff)
+find_closing_paren (const char *str, size_t i, size_t len, size_t *poff,
+                   char *paren)
 {
-  enum
-  { st_init, st_squote, st_dquote } state = st_init;
+  enum { st_init, st_squote, st_dquote } state = st_init;
   size_t level = 1;
 
   for (; i < len; i++)
@@ -619,18 +724,23 @@ find_closing_cbrace (const char *str, size_t i, size_t 
len, size_t * poff)
        case st_init:
          switch (str[i])
            {
-           case '{':
-             level++;
-             break;
-
-           case '}':
-             if (--level == 0)
+           default:
+             if (str[i] == paren[0])
+               {
+                 level++;
+                 break;
+               }
+             else if (str[i] == paren[1])
                {
-                 *poff = i;
-                 return 0;
+                 if (--level == 0)
+                   {
+                     *poff = i;
+                     return 0;
+                   }
+                 break;
                }
              break;
-
+             
            case '"':
              state = st_dquote;
              break;
@@ -657,13 +767,14 @@ find_closing_cbrace (const char *str, size_t i, size_t 
len, size_t * poff)
   return 1;
 }
 
-static const char *
-mu_wordsplit_find_env (struct mu_wordsplit *wsp, const char *name, size_t len)
+static int
+mu_wordsplit_find_env (struct mu_wordsplit *wsp, const char *name, size_t len,
+                   char const **ret)
 {
   size_t i;
 
   if (!(wsp->ws_flags & MU_WRDSF_ENV))
-    return NULL;
+    return MU_WRDSE_UNDEF;
 
   if (wsp->ws_flags & MU_WRDSF_ENV_KV)
     {
@@ -672,29 +783,139 @@ mu_wordsplit_find_env (struct mu_wordsplit *wsp, const 
char *name, size_t len)
        {
          size_t elen = strlen (wsp->ws_env[i]);
          if (elen == len && memcmp (wsp->ws_env[i], name, elen) == 0)
-           return wsp->ws_env[i + 1];
+           {
+             *ret = wsp->ws_env[i + 1];
+             return MU_WRDSE_OK;
+           }
          /* Skip the value.  Break the loop if it is NULL. */
          i++;
          if (wsp->ws_env[i] == NULL)
            break;
        }
     }
-  else
+  else if (wsp->ws_env)
     {
       /* Usual (A=B) environment. */
       for (i = 0; wsp->ws_env[i]; i++)
        {
          size_t j;
          const char *var = wsp->ws_env[i];
-         
+
          for (j = 0; j < len; j++)
            if (name[j] != var[j])
              break;
          if (j == len && var[j] == '=')
-           return var + j + 1;
+           {
+             *ret = var + j + 1;
+             return MU_WRDSE_OK;
+           }
+       }
+    }
+  return MU_WRDSE_UNDEF;
+}
+
+static int
+wsplt_assign_var (struct mu_wordsplit *wsp, const char *name, size_t namelen,
+                 char *value)
+{
+  int n = (wsp->ws_flags & MU_WRDSF_ENV_KV) ? 2 : 1;
+  char *v;
+  
+  if (wsp->ws_envidx + n >= wsp->ws_envsiz)
+    {
+      size_t sz;
+      char **newenv;
+
+      if (!wsp->ws_envbuf)
+       {
+         if (wsp->ws_flags & MU_WRDSF_ENV)
+           {
+             size_t i = 0, j;
+
+             if (wsp->ws_env)
+               {
+                 for (; wsp->ws_env[i]; i++)
+                   ;
+               }
+             
+             sz = i + n + 1;
+
+             newenv = calloc (sz, sizeof(newenv[0]));
+             if (!newenv)
+               return _wsplt_nomem (wsp);
+
+             for (j = 0; j < i; j++)
+               {
+                 newenv[j] = strdup (wsp->ws_env[j]);
+                 if (!newenv[j])
+                   {
+                     for (; j > 1; j--)
+                       free (newenv[j-1]);
+                     free (newenv[j-1]);
+                     return _wsplt_nomem (wsp);
+                   }
+               }
+             newenv[j] = NULL;
+             
+             wsp->ws_envbuf = newenv;
+             wsp->ws_envidx = i;
+             wsp->ws_envsiz = sz;
+             wsp->ws_env = (const char**) wsp->ws_envbuf;
+           }
+         else
+           {
+             newenv = calloc (MU_WORDSPLIT_ENV_INIT, sizeof(newenv[0]));
+             if (!newenv)
+               return _wsplt_nomem (wsp);
+             wsp->ws_envbuf = newenv;
+             wsp->ws_envidx = 0;
+             wsp->ws_envsiz = MU_WORDSPLIT_ENV_INIT;
+             wsp->ws_env = (const char**) wsp->ws_envbuf;
+             wsp->ws_flags |= MU_WRDSF_ENV;
+           }
+       }
+      else
+       {
+         wsp->ws_envsiz *= 2;
+         newenv = realloc (wsp->ws_envbuf,
+                           wsp->ws_envsiz * sizeof (wsp->ws_envbuf[0]));
+         if (!newenv)
+           return _wsplt_nomem (wsp);
+         wsp->ws_envbuf = newenv;
+         wsp->ws_env = (const char**) wsp->ws_envbuf;
+       }
+    }
+  
+  if (wsp->ws_flags & MU_WRDSF_ENV_KV)
+    {
+      /* A key-value pair environment */
+      char *p = malloc (namelen + 1);
+      if (!p)
+       return _wsplt_nomem (wsp);
+      memcpy (p, name, namelen);
+      p[namelen] = 0;
+
+      v = strdup (value);
+      if (!v)
+       {
+         free (p);
+         return _wsplt_nomem (wsp);
        }
+      wsp->ws_env[wsp->ws_envidx++] = p;
+      wsp->ws_env[wsp->ws_envidx++] = v;
+    }
+  else
+    {
+      v = malloc (namelen + strlen(value) + 2);
+      if (!v)
+       return _wsplt_nomem (wsp);
+      memcpy (v, name, namelen);
+      v[namelen++] = '=';
+      strcpy(v + namelen, value);
+      wsp->ws_env[wsp->ws_envidx++] = v;
     }
-  return NULL;
+  wsp->ws_env[wsp->ws_envidx++] = NULL;
+  return MU_WRDSE_OK;
 }
 
 static int
@@ -703,15 +924,17 @@ expvar (struct mu_wordsplit *wsp, const char *str, size_t 
len,
 {
   size_t i = 0;
   const char *defstr = NULL;
-  const char *value;
+  char *value;
   const char *vptr;
   struct mu_wordsplit_node *newnode;
   const char *start = str - 1;
-
-  if (ISALPHA (str[0]) || str[0] == '_')
+  int rc;
+  struct mu_wordsplit ws;
+  
+  if (ISVARBEG (str[0]))
     {
       for (i = 1; i < len; i++)
-       if (!(ISALNUM (str[i]) || str[i] == '_'))
+       if (!ISVARCHR (str[i]))
          break;
       *pend = str + i - 1;
     }
@@ -720,30 +943,36 @@ expvar (struct mu_wordsplit *wsp, const char *str, size_t 
len,
       str++;
       len--;
       for (i = 1; i < len; i++)
-       if (str[i] == '}' || str[i] == ':')
-         break;
-      if (str[i] == ':')
        {
-         size_t j;
-
-         defstr = str + i + 1;
-         if (find_closing_cbrace (str, i + 1, len, &j))
+         if (str[i] == ':')
            {
-             wsp->ws_errno = MU_WRDSE_CBRACE;
-             return 1;
+             size_t j;
+             
+             defstr = str + i + 1;
+             if (find_closing_paren (str, i + 1, len, &j, "{}"))
+               return _wsplt_seterr (wsp, MU_WRDSE_CBRACE);
+             *pend = str + j;
+             break;
+           }
+         else if (str[i] == '}')
+           {
+             defstr = NULL;
+             *pend = str + i;
+             break;
+           }
+         else if (strchr ("-+?=", str[i]))
+           {
+             size_t j;
+             
+             defstr = str + i;
+             if (find_closing_paren (str, i, len, &j, "{}"))
+               return _wsplt_seterr (wsp, MU_WRDSE_CBRACE);
+             *pend = str + j;
+             break;
            }
-         *pend = str + j;
-       }
-      else if (str[i] == '}')
-       {
-         defstr = NULL;
-         *pend = str + i;
-       }
-      else
-       {
-         wsp->ws_errno = MU_WRDSE_CBRACE;
-         return 1;
        }
+      if (i == len)
+       return _wsplt_seterr (wsp, MU_WRDSE_CBRACE);
     }
   else
     {
@@ -767,32 +996,135 @@ expvar (struct mu_wordsplit *wsp, const char *str, 
size_t len,
      i   - its length
      defstr - default replacement str */
 
-  vptr = mu_wordsplit_find_env (wsp, str, i);
-  if (vptr)
+  if (defstr && strchr("-+?=", defstr[0]) == 0)
     {
-      value = strdup (vptr);
-      if (!value)
-       return _wsplt_nomem (wsp);
+      rc = MU_WRDSE_UNDEF;
+      defstr = NULL;
     }
-  else if (wsp->ws_flags & MU_WRDSF_GETVAR)
-    value = wsp->ws_getvar (str, i, wsp->ws_closure);
-  else if (wsp->ws_flags & MU_WRDSF_UNDEF)
+  else
     {
-      wsp->ws_errno = MU_WRDSE_UNDEF;
-      if (wsp->ws_flags & MU_WRDSF_SHOWERR)
-       mu_wordsplit_perror (wsp);
-      return 1;
+      rc = mu_wordsplit_find_env (wsp, str, i, &vptr);
+      if (rc == MU_WRDSE_OK)
+       {
+         value = strdup (vptr);
+         if (!value)
+           rc = MU_WRDSE_NOSPACE;
+       }
+      else if (wsp->ws_flags & MU_WRDSF_GETVAR)
+       rc = wsp->ws_getvar (&value, str, i, wsp->ws_closure);
+      else
+       rc = MU_WRDSE_UNDEF;
+
+      if (rc == MU_WRDSE_OK && value[0] == 0 && defstr && defstr[-1] == ':')
+       {
+         free (value);
+         rc = MU_WRDSE_UNDEF;
+       }
     }
-  else
+  
+  switch (rc)
     {
-      if (wsp->ws_flags & MU_WRDSF_WARNUNDEF)
-       wsp->ws_error (_("warning: undefined variable `%.*s'"), (int) i, str);
-      if (wsp->ws_flags & MU_WRDSF_KEEPUNDEF)
-       value = NULL;
+    case MU_WRDSE_OK:
+      if (defstr && *defstr == '+')
+       {
+         size_t size = *pend - ++defstr;
+
+         rc = _wsplt_subsplit (wsp, &ws, defstr, size,
+                               MU_WRDSF_NOSPLIT | MU_WRDSF_WS | MU_WRDSF_QUOTE 
|
+                               (wsp->ws_flags &
+                                (MU_WRDSF_NOVAR | MU_WRDSF_NOCMD)));
+         if (rc)
+           return rc;
+         free (value);
+         value = ws.ws_wordv[0];
+         ws.ws_wordv[0] = NULL;
+         mu_wordsplit_free (&ws);
+       }
+      break;
+      
+    case MU_WRDSE_UNDEF:
+      if (defstr)
+       {
+         size_t size;
+         if (*defstr == '-' || *defstr == '=')
+           {
+             size = *pend - ++defstr;
+
+             rc = _wsplt_subsplit (wsp, &ws, defstr, size,
+                                   MU_WRDSF_NOSPLIT | MU_WRDSF_WS | 
MU_WRDSF_QUOTE |
+                                   (wsp->ws_flags &
+                                    (MU_WRDSF_NOVAR | MU_WRDSF_NOCMD)));
+             if (rc)
+               return rc;
+
+             value = ws.ws_wordv[0];
+             ws.ws_wordv[0] = NULL;
+             mu_wordsplit_free (&ws);
+             
+             if (defstr[-1] == '=')
+               wsplt_assign_var (wsp, str, i, value);
+           }
+         else 
+           {
+             if (*defstr == '?')
+               {
+                 size = *pend - ++defstr;
+                 if (size == 0)
+                   wsp->ws_error (_("%.*s: variable null or not set"),
+                                  (int) i, str);
+                 else
+                   {
+                     rc = _wsplt_subsplit (wsp, &ws, defstr, size,
+                                           MU_WRDSF_NOSPLIT | MU_WRDSF_WS |
+                                           MU_WRDSF_QUOTE |
+                                           (wsp->ws_flags &
+                                            (MU_WRDSF_NOVAR | 
MU_WRDSF_NOCMD)));
+                     if (rc == 0)
+                       wsp->ws_error ("%.*s: %s",
+                                      (int) i, str, ws.ws_wordv[0]);
+                     else
+                       wsp->ws_error (_("%.*s: %.*s"),
+                                      (int) i, str, (int) size, defstr);
+                     mu_wordsplit_free (&ws);
+                   }
+               }
+             value = NULL;
+           }
+       }
+      else if (wsp->ws_flags & MU_WRDSF_UNDEF)
+       {
+         _wsplt_seterr (wsp, MU_WRDSE_UNDEF);
+         return 1;
+       }
       else
-       value = "";
+       {
+         if (wsp->ws_flags & MU_WRDSF_WARNUNDEF)
+           wsp->ws_error (_("warning: undefined variable `%.*s'"),
+                          (int) i, str);
+         if (wsp->ws_flags & MU_WRDSF_KEEPUNDEF)
+           value = NULL;
+         else
+           {
+             value = strdup ("");
+             if (!value)
+               return _wsplt_nomem (wsp);
+           }
+       }
+      break;
+      
+    case MU_WRDSE_NOSPACE:
+      return _wsplt_nomem (wsp);
+
+    case MU_WRDSE_USERERR:
+      if (wsp->ws_errno == MU_WRDSE_USERERR)
+       free (wsp->ws_usererr);
+      wsp->ws_usererr = value;
+      /* fall through */
+    default:
+      _wsplt_seterr (wsp, rc);
+      return 1;
     }
-  /* FIXME: handle defstr */
+
   if (value)
     {
       if (flg & _WSNF_QUOTE)
@@ -802,12 +1134,11 @@ expvar (struct mu_wordsplit *wsp, const char *str, 
size_t len,
          wsnode_insert (wsp, newnode, *ptail, 0);
          *ptail = newnode;
          newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg;
-         newnode->v.word = strdup (value);
-         if (!newnode->v.word)
-           return _wsplt_nomem (wsp);
+         newnode->v.word = value;
        }
       else if (*value == 0)
        {
+         free (value);
          /* Empty string is a special case */
          if (wsnode_new (wsp, &newnode))
            return 1;
@@ -818,13 +1149,15 @@ expvar (struct mu_wordsplit *wsp, const char *str, 
size_t len,
       else
        {
          struct mu_wordsplit ws;
-         int i;
-
-         ws.ws_delim = wsp->ws_delim;
-         if (mu_wordsplit (value, &ws,
-                           MU_WRDSF_NOVAR | MU_WRDSF_NOCMD |
-                           MU_WRDSF_DELIM | MU_WRDSF_WS))
+         int i, rc;
+         
+         rc = _wsplt_subsplit (wsp, &ws, value, strlen (value),
+                               MU_WRDSF_NOVAR | MU_WRDSF_NOCMD |
+                               MU_WRDSF_QUOTE);
+         free (value);
+         if (rc)
            {
+             _wsplt_seterr_sub (wsp, &ws);
              mu_wordsplit_free (&ws);
              return 1;
            }
@@ -871,7 +1204,19 @@ expvar (struct mu_wordsplit *wsp, const char *str, size_t 
len,
 }
 
 static int
-node_expand_vars (struct mu_wordsplit *wsp, struct mu_wordsplit_node *node)
+begin_var_p (int c)
+{
+  return c == '{' || ISVARBEG (c);
+}
+
+static int
+node_expand (struct mu_wordsplit *wsp, struct mu_wordsplit_node *node,
+            int (*beg_p) (int),
+            int (*ws_exp_fn) (struct mu_wordsplit *wsp,
+                              const char *str, size_t len,
+                              struct mu_wordsplit_node **ptail,
+                              const char **pend,
+                              int flg))
 {
   const char *str = wsnode_ptr (wsp, node);
   size_t slen = wsnode_len (node);
@@ -887,7 +1232,7 @@ node_expand_vars (struct mu_wordsplit *wsp, struct 
mu_wordsplit_node *node)
          p++;
          continue;
        }
-      if (*p == '$')
+      if (*p == '$' && beg_p (p[1]))
        {
          size_t n = p - str;
 
@@ -896,8 +1241,8 @@ node_expand_vars (struct mu_wordsplit *wsp, struct 
mu_wordsplit_node *node)
          if (node_split_prefix (wsp, &tail, node, off, n, _WSNF_JOIN))
            return 1;
          p++;
-         if (expvar (wsp, p, slen - n, &tail, &p,
-                     node->flags & (_WSNF_JOIN | _WSNF_QUOTE)))
+         if (ws_exp_fn (wsp, p, slen - n, &tail, &p,
+                        node->flags & (_WSNF_JOIN | _WSNF_QUOTE)))
            return 1;
          off += p - str + 1;
          str = p + 1;
@@ -908,7 +1253,7 @@ node_expand_vars (struct mu_wordsplit *wsp, struct 
mu_wordsplit_node *node)
       if (tail != node)
        tail->flags |= _WSNF_JOIN;
       if (node_split_prefix (wsp, &tail, node, off, p - str,
-                            node->flags & _WSNF_JOIN))
+                            node->flags & (_WSNF_JOIN|_WSNF_QUOTE)))
        return 1;
     }
   if (tail != node)
@@ -918,8 +1263,8 @@ node_expand_vars (struct mu_wordsplit *wsp, struct 
mu_wordsplit_node *node)
     }
   return 0;
 }
-
-/* Remove NULL lists */
+  
+/* Remove NULL nodes from the list */
 static void
 wsnode_nullelim (struct mu_wordsplit *wsp)
 {
@@ -928,6 +1273,8 @@ wsnode_nullelim (struct mu_wordsplit *wsp)
   for (p = wsp->ws_head; p;)
     {
       struct mu_wordsplit_node *next = p->next;
+      if (p->flags & _WSNF_DELIM && p->prev)
+       p->prev->flags &= ~_WSNF_JOIN;
       if (p->flags & _WSNF_NULL)
        {
          wsnode_remove (wsp, p);
@@ -946,7 +1293,7 @@ mu_wordsplit_varexp (struct mu_wordsplit *wsp)
     {
       struct mu_wordsplit_node *next = p->next;
       if (!(p->flags & _WSNF_NOEXPAND))
-       if (node_expand_vars (wsp, p))
+       if (node_expand (wsp, p, begin_var_p, expvar))
          return 1;
       p = next;
     }
@@ -955,72 +1302,408 @@ mu_wordsplit_varexp (struct mu_wordsplit *wsp)
   return 0;
 }
 
-/* Strip off any leading and trailing whitespace.  This function is called
-   right after the initial scanning, therefore it assumes that every
-   node in the list is a text reference node. */ 
-static void
-mu_wordsplit_trimws (struct mu_wordsplit *wsp)
+static int
+begin_cmd_p (int c)
 {
-  struct mu_wordsplit_node *p;
-
-  for (p = wsp->ws_head; p; p = p->next)
-    {
-      size_t n;
-
-      if (p->flags & _WSNF_QUOTE)
-       continue;
-      
-      /* Skip leading whitespace: */
-      for (n = p->v.segm.beg; n < p->v.segm.end && ISWS (wsp->ws_input[n]);
-          n++)
-       ;
-      p->v.segm.beg = n;
-      /* Trim trailing whitespace */
-      for (n = p->v.segm.end; n > p->v.segm.beg && ISWS (wsp->ws_input[n-1]);
-          n--);
-      p->v.segm.end = n;
-      if (p->v.segm.beg == p->v.segm.end)
-       p->flags |= _WSNF_NULL;
-    }
-
-  wsnode_nullelim (wsp);
+  return c == '(';
 }
-
+
 static int
-skip_sed_expr (const char *command, size_t i, size_t len)
+expcmd (struct mu_wordsplit *wsp, const char *str, size_t len,
+       struct mu_wordsplit_node **ptail, const char **pend, int flg)
 {
-  int state;
+  int rc;
+  size_t j;
+  char *value;
+  struct mu_wordsplit_node *newnode;
+  
+  str++;
+  len--;
 
-  do
+  if (find_closing_paren (str, 0, len, &j, "()"))
     {
-      int delim;
+      _wsplt_seterr (wsp, MU_WRDSE_PAREN);
+      return 1;
+    }
 
-      if (command[i] == ';')
-       i++;
-      if (!(command[i] == 's' && i + 3 < len && ISPUNCT (command[i + 1])))
-       break;
+  *pend = str + j;
+  if (wsp->ws_options & MU_WRDSO_ARGV)
+    {
+      struct mu_wordsplit ws;
 
-      delim = command[++i];
-      state = 1;
-      for (i++; i < len; i++)
+      rc = _wsplt_subsplit (wsp, &ws, str, j,
+                           MU_WRDSF_NOVAR | MU_WRDSF_NOCMD |
+                           MU_WRDSF_WS | MU_WRDSF_QUOTE);
+      if (rc)
        {
-         if (state == 3)
-           {
-             if (command[i] == delim || !ISALNUM (command[i]))
-               break;
-           }
-         else if (command[i] == '\\')
-           i++;
-         else if (command[i] == delim)
-           state++;
+         _wsplt_seterr_sub (wsp, &ws);
+         mu_wordsplit_free (&ws);
+         return 1;
        }
+      rc = wsp->ws_command (&value, str, j, ws.ws_wordv, wsp->ws_closure);
+      mu_wordsplit_free (&ws);
     }
-  while (state == 3 && i < len && command[i] == ';');
-  return i;
-}
-
-static size_t
-skip_delim (struct mu_wordsplit *wsp)
+  else
+    rc = wsp->ws_command (&value, str, j, NULL, wsp->ws_closure);
+  
+  if (rc == MU_WRDSE_NOSPACE)
+    return _wsplt_nomem (wsp);
+  else if (rc)
+    {
+      if (rc == MU_WRDSE_USERERR)
+       {
+         if (wsp->ws_errno == MU_WRDSE_USERERR)
+           free (wsp->ws_usererr);
+         wsp->ws_usererr = value;
+       }
+      _wsplt_seterr (wsp, rc);
+      return 1;
+    }
+
+  if (value)
+    {
+      if (flg & _WSNF_QUOTE)
+       {
+         if (wsnode_new (wsp, &newnode))
+           return 1;
+         wsnode_insert (wsp, newnode, *ptail, 0);
+         *ptail = newnode;
+         newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg;
+         newnode->v.word = value;
+       }
+      else if (*value == 0)
+       {
+         free (value);
+         /* Empty string is a special case */
+         if (wsnode_new (wsp, &newnode))
+           return 1;
+         wsnode_insert (wsp, newnode, *ptail, 0);
+         *ptail = newnode;
+         newnode->flags = _WSNF_NULL;
+       }
+      else
+       {
+         struct mu_wordsplit ws;
+         int i, rc;
+
+         rc = _wsplt_subsplit (wsp, &ws, value, strlen (value),
+                               MU_WRDSF_NOVAR | MU_WRDSF_NOCMD |
+                               MU_WRDSF_WS | MU_WRDSF_QUOTE);
+         free (value);
+         if (rc)
+           {
+             _wsplt_seterr_sub (wsp, &ws);
+             mu_wordsplit_free (&ws);
+             return 1;
+           }
+         for (i = 0; i < ws.ws_wordc; i++)
+           {
+             if (wsnode_new (wsp, &newnode))
+               return 1;
+             wsnode_insert (wsp, newnode, *ptail, 0);
+             *ptail = newnode;
+             newnode->flags = _WSNF_WORD |
+               _WSNF_NOEXPAND |
+               (i + 1 < ws.ws_wordc ? (flg & ~_WSNF_JOIN) : flg);
+             newnode->v.word = strdup (ws.ws_wordv[i]);
+             if (!newnode->v.word)
+               return _wsplt_nomem (wsp);
+           }
+         mu_wordsplit_free (&ws);
+       }
+    }
+  else
+    {
+      if (wsnode_new (wsp, &newnode))
+       return 1;
+      wsnode_insert (wsp, newnode, *ptail, 0);
+      *ptail = newnode;
+      newnode->flags = _WSNF_NULL;
+    }
+  return 0;
+}
+
+static int
+mu_wordsplit_cmdexp (struct mu_wordsplit *wsp)
+{
+  struct mu_wordsplit_node *p;
+
+  for (p = wsp->ws_head; p;)
+    {
+      struct mu_wordsplit_node *next = p->next;
+      if (!(p->flags & _WSNF_NOEXPAND))
+       if (node_expand (wsp, p, begin_cmd_p, expcmd))
+         return 1;
+      p = next;
+    }
+
+  wsnode_nullelim (wsp);
+  return 0;
+}
+
+/* Strip off any leading and trailing whitespace.  This function is called
+   right after the initial scanning, therefore it assumes that every
+   node in the list is a text reference node. */
+static int
+mu_wordsplit_trimws (struct mu_wordsplit *wsp)
+{
+  struct mu_wordsplit_node *p;
+
+  for (p = wsp->ws_head; p; p = p->next)
+    {
+      size_t n;
+
+      if (!(p->flags & _WSNF_QUOTE))
+       {
+         /* Skip leading whitespace: */
+         for (n = p->v.segm.beg; n < p->v.segm.end && ISWS (wsp->ws_input[n]);
+              n++)
+           ;
+         p->v.segm.beg = n;
+       }
+      
+      while (p->next && (p->flags & _WSNF_JOIN))
+       p = p->next;
+      
+      if (p->flags & _WSNF_QUOTE)
+       continue;
+      
+      /* Trim trailing whitespace */
+      for (n = p->v.segm.end;
+          n > p->v.segm.beg && ISWS (wsp->ws_input[n - 1]); n--);
+      p->v.segm.end = n;
+      if (p->v.segm.beg == p->v.segm.end)
+       p->flags |= _WSNF_NULL;
+    }
+
+  wsnode_nullelim (wsp);
+  return 0;
+}
+
+static int
+mu_wordsplit_tildexpand (struct mu_wordsplit *wsp)
+{
+  struct mu_wordsplit_node *p;
+  char *uname = NULL;
+  size_t usize = 0;
+  
+  for (p = wsp->ws_head; p; p = p->next)
+    {
+      const char *str;
+
+      if (p->flags & _WSNF_QUOTE)
+       continue;
+
+      str = wsnode_ptr (wsp, p);
+      if (str[0] == '~')
+       {
+         size_t i, size, dlen;
+         size_t slen = wsnode_len (p);
+         struct passwd *pw;
+         char *newstr;
+         
+         for (i = 1; i < slen && str[i] != '/'; i++)
+           ;
+         if (i == slen)
+           continue;
+         if (i > 1)
+           {
+             if (i > usize)
+               {
+                 char *p = realloc (uname, i);
+                 if (!p)
+                   {
+                     free (uname);
+                     return _wsplt_nomem (wsp);
+                   }
+                 uname = p;
+                 usize = i;
+               }
+             --i;
+             memcpy (uname, str + 1, i);
+             uname[i] = 0;
+             pw = getpwnam (uname);
+           }
+         else
+           pw = getpwuid (getuid ());
+
+         if (!pw)
+           continue;
+
+         dlen = strlen (pw->pw_dir);
+         size = slen - i + dlen;
+         newstr = malloc (size);
+         if (!newstr)
+           {
+             free (uname);
+             return _wsplt_nomem (wsp);
+           }
+         --size;
+
+         memcpy (newstr, pw->pw_dir, dlen);
+         memcpy (newstr + dlen, str + i + 1, slen - i - 1);
+         newstr[size] = 0;
+         if (p->flags & _WSNF_WORD)
+           free (p->v.word);
+         p->v.word = newstr;
+         p->flags |= _WSNF_WORD;
+       }
+    }
+  free (uname);
+  return 0;
+}
+
+static int
+isglob (const char *s, int l)
+{
+  while (l--)
+    {
+      if (strchr ("*?[", *s++))
+       return 1;
+    }
+  return 0;
+}
+
+static int
+mu_wordsplit_pathexpand (struct mu_wordsplit *wsp)
+{
+  struct mu_wordsplit_node *p, *next;
+  char *pattern = NULL;
+  size_t patsize = 0;
+  size_t slen;
+  int flags = 0;
+
+#ifdef GLOB_PERIOD
+  if (wsp->ws_options & MU_WRDSO_DOTGLOB)
+    flags = GLOB_PERIOD;
+#endif
+  
+  for (p = wsp->ws_head; p; p = next)
+    {
+      const char *str;
+
+      next = p->next;
+
+      if (p->flags & _WSNF_QUOTE)
+       continue;
+
+      str = wsnode_ptr (wsp, p);
+      slen = wsnode_len (p);
+
+      if (isglob (str, slen))
+       {
+         int i;
+         glob_t g;
+         struct mu_wordsplit_node *prev;
+         
+         if (slen + 1 > patsize)
+           {
+             char *p = realloc (pattern, slen + 1);
+             if (!p)
+               return _wsplt_nomem (wsp);
+             pattern = p;
+             patsize = slen + 1;
+           }
+         memcpy (pattern, str, slen);
+         pattern[slen] = 0;
+      
+         switch (glob (pattern, flags, NULL, &g))
+           {
+           case 0:
+             break;
+             
+           case GLOB_NOSPACE:
+             free (pattern);
+             return _wsplt_nomem (wsp);
+             
+           case GLOB_NOMATCH:
+             if (wsp->ws_options & MU_WRDSO_NULLGLOB)
+               {
+                 wsnode_remove (wsp, p);
+                 wsnode_free (p);
+               }
+             else if (wsp->ws_options & MU_WRDSO_FAILGLOB)
+               {
+                 char buf[128];
+                 if (wsp->ws_errno == MU_WRDSE_USERERR)
+                   free (wsp->ws_usererr);
+                 snprintf (buf, sizeof (buf), _("no files match pattern %s"),
+                           pattern);
+                 free (pattern);
+                 wsp->ws_usererr = strdup (buf);
+                 if (!wsp->ws_usererr)
+                   return _wsplt_nomem (wsp);
+                 else
+                   return _wsplt_seterr (wsp, MU_WRDSE_USERERR);
+               }
+             continue;
+             
+           default:
+             free (pattern);
+             return _wsplt_seterr (wsp, MU_WRDSE_GLOBERR);
+           }
+
+         prev = p;
+         for (i = 0; i < g.gl_pathc; i++)
+           {
+             struct mu_wordsplit_node *newnode;
+             char *newstr;
+             
+             if (wsnode_new (wsp, &newnode))
+               return 1;
+             newstr = strdup (g.gl_pathv[i]);
+             if (!newstr)
+               return _wsplt_nomem (wsp);
+             newnode->v.word = newstr;
+             newnode->flags |= _WSNF_WORD|_WSNF_QUOTE;
+             wsnode_insert (wsp, newnode, prev, 0);
+             prev = newnode;
+           }
+         globfree (&g);
+
+         wsnode_remove (wsp, p);
+         wsnode_free (p);
+       }
+    }
+  free (pattern);
+  return 0;
+}
+
+static int
+skip_sed_expr (const char *command, size_t i, size_t len)
+{
+  int state;
+
+  do
+    {
+      int delim;
+
+      if (command[i] == ';')
+       i++;
+      if (!(command[i] == 's' && i + 3 < len && ISPUNCT (command[i + 1])))
+       break;
+
+      delim = command[++i];
+      state = 1;
+      for (i++; i < len; i++)
+       {
+         if (state == 3)
+           {
+             if (command[i] == delim || !ISALNUM (command[i]))
+               break;
+           }
+         else if (command[i] == '\\')
+           i++;
+         else if (command[i] == delim)
+           state++;
+       }
+    }
+  while (state == 3 && i < len && command[i] == ';');
+  return i;
+}
+
+static size_t
+skip_delim (struct mu_wordsplit *wsp)
 {
   size_t start = wsp->ws_endp;
   if (wsp->ws_flags & MU_WRDSF_SQUEEZE_DELIMS)
@@ -1065,7 +1748,7 @@ scan_qstring (struct mu_wordsplit *wsp, size_t start, 
size_t * end)
       j++;
   if (j < len && command[j] == q)
     {
-      int flags = _WSNF_QUOTE|_WSNF_EMPTYOK;
+      int flags = _WSNF_QUOTE | _WSNF_EMPTYOK;
       if (q == '\'')
        flags |= _WSNF_NOEXPAND;
       if (mu_wordsplit_add_segm (wsp, start + 1, j, flags))
@@ -1075,9 +1758,7 @@ scan_qstring (struct mu_wordsplit *wsp, size_t start, 
size_t * end)
   else
     {
       wsp->ws_endp = start;
-      wsp->ws_errno = MU_WRDSE_QUOTE;
-      if (wsp->ws_flags & MU_WRDSF_SHOWERR)
-       mu_wordsplit_perror (wsp);
+      _wsplt_seterr (wsp, MU_WRDSE_QUOTE);
       return _MU_WRDS_ERR;
     }
   return 0;
@@ -1147,6 +1828,18 @@ scan_word (struct mu_wordsplit *wsp, size_t start)
                }
            }
 
+         if (command[i] == '$')
+           {
+             if (!(wsp->ws_flags & MU_WRDSF_NOVAR)
+                 && command[i+1] == '{'
+                 && find_closing_paren (command, i + 2, len, &i, "{}") == 0)
+               continue;
+             if (!(wsp->ws_flags & MU_WRDSF_NOCMD)
+                 && command[i+1] == '('
+                 && find_closing_paren (command, i + 2, len, &i, "()") == 0)
+               continue;
+           }
+
          if (ISDELIM (wsp, command[i]))
            break;
          else
@@ -1156,6 +1849,7 @@ scan_word (struct mu_wordsplit *wsp, size_t start)
   else if (wsp->ws_flags & MU_WRDSF_RETURN_DELIMS)
     {
       i++;
+      flags |= _WSNF_DELIM;
     }
   else if (!(wsp->ws_flags & MU_WRDSF_SQUEEZE_DELIMS))
     flags |= _WSNF_EMPTYOK;
@@ -1170,35 +1864,6 @@ scan_word (struct mu_wordsplit *wsp, size_t start)
   return _MU_WRDS_OK;
 }
 
-static char quote_transtab[] = "\\\\a\ab\bf\fn\nr\rt\tv\v";
-
-int
-mu_wordsplit_c_unquote_char (int c)
-{
-  char *p;
-
-  for (p = quote_transtab; *p; p += 2)
-    {
-      if (*p == c)
-       return p[1];
-    }
-  return c;
-}
-
-int
-mu_wordsplit_c_quote_char (int c)
-{
-  char *p;
-
-  for (p = quote_transtab + sizeof (quote_transtab) - 2;
-       p > quote_transtab; p -= 2)
-    {
-      if (*p == c)
-       return p[-1];
-    }
-  return -1;
-}
-
 #define to_num(c) \
   (ISDIGIT(c) ? c - '0' : (ISXDIGIT(c) ? toupper(c) - 'A' + 10 : 255 ))
 
@@ -1228,7 +1893,7 @@ mu_wordsplit_c_quoted_length (const char *str, int 
quote_hex, int *quote)
     {
       if (strchr (" \"", *str))
        *quote = 1;
-      
+
       if (*str == ' ')
        len++;
       else if (*str == '"')
@@ -1239,7 +1904,7 @@ mu_wordsplit_c_quoted_length (const char *str, int 
quote_hex, int *quote)
        len += 3;
       else
        {
-         if (mu_wordsplit_c_quote_char (*str) != -1)
+         if (mu_wordsplit_c_quote_char (*str))
            len += 2;
          else
            len += 4;
@@ -1248,47 +1913,56 @@ mu_wordsplit_c_quoted_length (const char *str, int 
quote_hex, int *quote)
   return len;
 }
 
-void
-mu_wordsplit_general_unquote_copy (char *dst, const char *src, size_t n,
-                                  const char *escapable)
+int
+wsplt_unquote_char (const char *transtab, int c)
 {
-  int i;
-
-  for (i = 0; i < n;)
+  while (*transtab && transtab[1])
     {
-      if (src[i] == '\\' && i < n && strchr (escapable, src[i+1]))
-       i++;
-      *dst++ = src[i++];
+      if (*transtab++ == c)
+       return *transtab;
+      ++transtab;
     }
-  *dst = 0;
+  return 0;
 }
 
-void
-mu_wordsplit_sh_unquote_copy (char *dst, const char *src, size_t n)
+int
+wsplt_quote_char (const char *transtab, int c)
 {
-  int i;
-
-  for (i = 0; i < n;)
+  for (; *transtab && transtab[1]; transtab += 2)
     {
-      if (src[i] == '\\')
-       i++;
-      *dst++ = src[i++];
+      if (transtab[1] == c)
+       return *transtab;
     }
-  *dst = 0;
+  return 0;
+}
+
+int
+mu_wordsplit_c_unquote_char (int c)
+{
+  return wsplt_unquote_char (mu_wordsplit_c_escape_tab, c);
+}
+
+int
+mu_wordsplit_c_quote_char (int c)
+{
+  return wsplt_quote_char (mu_wordsplit_c_escape_tab, c);
 }
 
 void
-mu_wordsplit_c_unquote_copy (char *dst, const char *src, size_t n)
+mu_wordsplit_string_unquote_copy (struct mu_wordsplit *ws, int inquote,
+                                 char *dst, const char *src, size_t n)
 {
   int i = 0;
   int c;
 
+  inquote = !!inquote;
   while (i < n)
     {
       if (src[i] == '\\')
        {
          ++i;
-         if (src[i] == 'x' || src[i] == 'X')
+         if (MU_WRDSO_ESC_TEST (ws, inquote, MU_WRDSO_XESC)
+             && (src[i] == 'x' || src[i] == 'X'))
            {
              if (n - i < 2)
                {
@@ -1311,7 +1985,8 @@ mu_wordsplit_c_unquote_copy (char *dst, const char *src, 
size_t n)
                    }
                }
            }
-         else if ((unsigned char) src[i] < 128 && ISDIGIT (src[i]))
+         else if (MU_WRDSO_ESC_TEST (ws, inquote, MU_WRDSO_OESC)
+                  && (unsigned char) src[i] < 128 && ISDIGIT (src[i]))
            {
              if (n - i < 1)
                {
@@ -1333,8 +2008,17 @@ mu_wordsplit_c_unquote_copy (char *dst, const char *src, 
size_t n)
                    }
                }
            }
+         else if ((c = wsplt_unquote_char (ws->ws_escape[inquote], src[i])))
+           {
+             *dst++ = c;
+             ++i;
+           }
          else
-           *dst++ = mu_wordsplit_c_unquote_char (src[i++]);
+           {
+             if (MU_WRDSO_ESC_TEST (ws, inquote, MU_WRDSO_BSKEEP))
+               *dst++ = '\\';
+             *dst++ = src[i++];
+           }
        }
       else
        *dst++ = src[i++];
@@ -1368,7 +2052,7 @@ mu_wordsplit_c_quote_copy (char *dst, const char *src, 
int quote_hex)
            {
              int c = mu_wordsplit_c_quote_char (*src);
              *dst++ = '\\';
-             if (c != -1)
+             if (c)
                *dst++ = c;
              else
                {
@@ -1381,9 +2065,37 @@ mu_wordsplit_c_quote_copy (char *dst, const char *src, 
int quote_hex)
     }
 }
 
+struct exptab
+{
+  char *descr;
+  int flag;
+  int opt;
+  int (*expansion) (struct mu_wordsplit *wsp);
+};
+
+#define EXPOPT_NEG      0x01
+#define EXPOPT_COALESCE 0x02
+
+static struct exptab exptab[] = {
+  { N_("WS trimming"),          MU_WRDSF_WS,         0, mu_wordsplit_trimws },
+  { N_("tilde expansion"),      MU_WRDSF_PATHEXPAND, 0, 
mu_wordsplit_tildexpand },
+  { N_("variable expansion"),   MU_WRDSF_NOVAR,      EXPOPT_NEG,
+    mu_wordsplit_varexp },
+  { N_("quote removal"),        0,                EXPOPT_NEG,
+    wsnode_quoteremoval },
+  { N_("command substitution"), MU_WRDSF_NOCMD,      
EXPOPT_NEG|EXPOPT_COALESCE,
+    mu_wordsplit_cmdexp },
+  { N_("coalesce list"),        0,                EXPOPT_NEG|EXPOPT_COALESCE,
+    NULL },
+  { N_("path expansion"),       MU_WRDSF_PATHEXPAND, 0, 
mu_wordsplit_pathexpand },
+  { NULL }
+};
+    
 static int
-wordsplit_process_list (struct mu_wordsplit *wsp, size_t start)
+mu_wordsplit_process_list (struct mu_wordsplit *wsp, size_t start)
 {
+  struct exptab *p;
+  
   if (wsp->ws_flags & MU_WRDSF_NOSPLIT)
     {
       /* Treat entire input as a quoted argument */
@@ -1393,7 +2105,7 @@ wordsplit_process_list (struct mu_wordsplit *wsp, size_t 
start)
   else
     {
       int rc;
-  
+
       while ((rc = scan_word (wsp, start)) == _MU_WRDS_OK)
        start = skip_delim (wsp);
       /* Make sure tail element is not joinable */
@@ -1405,62 +2117,44 @@ wordsplit_process_list (struct mu_wordsplit *wsp, 
size_t start)
 
   if (wsp->ws_flags & MU_WRDSF_SHOWDBG)
     {
-      wsp->ws_debug ("Initial list:");
+      wsp->ws_debug ("(%02d) %s", wsp->ws_lvl, _("Initial list:"));
       mu_wordsplit_dump_nodes (wsp);
     }
 
-  if (wsp->ws_flags & MU_WRDSF_WS)
-    {
-      /* Trim leading and trailing whitespace */
-      mu_wordsplit_trimws (wsp);
-      if (wsp->ws_flags & MU_WRDSF_SHOWDBG)
-       {
-         wsp->ws_debug ("After WS trimming:");
-         mu_wordsplit_dump_nodes (wsp);
-       }
-    }
-  
-  /* Expand variables (FIXME: & commands) */
-  if (!(wsp->ws_flags & MU_WRDSF_NOVAR))
-    {
-      if (mu_wordsplit_varexp (wsp))
-       {
-         mu_wordsplit_free_nodes (wsp);
-         return wsp->ws_errno;
-       }
-      if (wsp->ws_flags & MU_WRDSF_SHOWDBG)
-       {
-         wsp->ws_debug ("Expanded list:");
-         mu_wordsplit_dump_nodes (wsp);
-       }
-    }
-
-  do
+  for (p = exptab; p->descr; p++)
     {
-      if (wsnode_quoteremoval (wsp))
-       break;
-      if (wsp->ws_flags & MU_WRDSF_SHOWDBG)
+      if ((p->opt & EXPOPT_NEG)
+         ? !(wsp->ws_flags & p->flag) : (wsp->ws_flags & p->flag))
        {
-         wsp->ws_debug ("After quote removal:");
-         mu_wordsplit_dump_nodes (wsp);
-       }
-
-      if (wsnode_coalesce (wsp))
-       break;
-
-      if (wsp->ws_flags & MU_WRDSF_SHOWDBG)
-       {
-         wsp->ws_debug ("Coalesced list:");
-         mu_wordsplit_dump_nodes (wsp);
+         if (p->opt & EXPOPT_COALESCE)
+           {
+             if (wsnode_coalesce (wsp))
+               break;
+             if (wsp->ws_flags & MU_WRDSF_SHOWDBG)
+               {
+                 wsp->ws_debug ("(%02d) %s", wsp->ws_lvl,
+                                _("Coalesced list:"));
+                 mu_wordsplit_dump_nodes (wsp);
+               }
+           }
+         if (p->expansion)
+           {
+             if (p->expansion (wsp))
+               break;
+             if (wsp->ws_flags & MU_WRDSF_SHOWDBG)
+               {
+                 wsp->ws_debug ("(%02d) %s", wsp->ws_lvl, _(p->descr));
+                 mu_wordsplit_dump_nodes (wsp);
+               }
+           }
        }
     }
-  while (0);
   return wsp->ws_errno;
-}  
+}
 
-int
-mu_wordsplit_len (const char *command, size_t length, struct mu_wordsplit *wsp,
-                 int flags)
+static int
+mu_wordsplit_run (const char *command, size_t length, struct mu_wordsplit *wsp,
+               int flags, int lvl)
 {
   int rc;
   size_t start;
@@ -1474,13 +2168,8 @@ mu_wordsplit_len (const char *command, size_t length, 
struct mu_wordsplit *wsp,
 
       start = skip_delim (wsp);
       if (wsp->ws_endp == wsp->ws_len)
-       {
-         wsp->ws_errno = MU_WRDSE_NOINPUT;
-         if (wsp->ws_flags & MU_WRDSF_SHOWERR)
-           mu_wordsplit_perror (wsp);
-         return wsp->ws_errno;
-       }
-      
+       return _wsplt_seterr (wsp, MU_WRDSE_NOINPUT);
+
       cmdptr = wsp->ws_input + wsp->ws_endp;
       cmdlen = wsp->ws_len - wsp->ws_endp;
       wsp->ws_flags |= MU_WRDSF_REUSE;
@@ -1494,12 +2183,13 @@ mu_wordsplit_len (const char *command, size_t length, 
struct mu_wordsplit *wsp,
       rc = mu_wordsplit_init (wsp, cmdptr, cmdlen, flags);
       if (rc)
        return rc;
+      wsp->ws_lvl = lvl;
     }
 
   if (wsp->ws_flags & MU_WRDSF_SHOWDBG)
-    wsp->ws_debug ("Input:%.*s;", (int)cmdlen, cmdptr);
+    wsp->ws_debug (_("(%02d) Input:%.*s;"), wsp->ws_lvl, (int) cmdlen, cmdptr);
 
-  rc = wordsplit_process_list (wsp, start);
+  rc = mu_wordsplit_process_list (wsp, start);
   if (rc == 0 && (flags & MU_WRDSF_INCREMENTAL))
     {
       while (!wsp->ws_head && wsp->ws_endp < wsp->ws_len)
@@ -1509,9 +2199,10 @@ mu_wordsplit_len (const char *command, size_t length, 
struct mu_wordsplit *wsp,
            {
              cmdptr = wsp->ws_input + wsp->ws_endp;
              cmdlen = wsp->ws_len - wsp->ws_endp;
-             wsp->ws_debug ("Restart:%.*s;", (int)cmdlen, cmdptr);
+             wsp->ws_debug (_("(%02d) Restart:%.*s;"),
+                            wsp->ws_lvl, (int) cmdlen, cmdptr);
            }
-         rc = wordsplit_process_list (wsp, start);
+         rc = mu_wordsplit_process_list (wsp, start);
          if (rc)
            break;
        }
@@ -1527,6 +2218,13 @@ mu_wordsplit_len (const char *command, size_t length, 
struct mu_wordsplit *wsp,
 }
 
 int
+mu_wordsplit_len (const char *command, size_t length, struct mu_wordsplit 
*wsp, 
+               int flags)
+{
+  return mu_wordsplit_run (command, length, wsp, flags, 0);
+}
+
+int
 mu_wordsplit (const char *command, struct mu_wordsplit *ws, int flags)
 {
   return mu_wordsplit_len (command, command ? strlen (command) : 0, ws, flags);
@@ -1550,65 +2248,62 @@ mu_wordsplit_free_words (struct mu_wordsplit *ws)
 }
 
 void
+mu_wordsplit_free_envbuf (struct mu_wordsplit *ws)
+{
+  if (ws->ws_flags & MU_WRDSF_NOCMD)
+    return;
+  if (ws->ws_envbuf)
+    {
+      size_t i;
+
+      for (i = 0; ws->ws_envbuf[i]; i++)
+       free (ws->ws_envbuf[i]);
+      free (ws->ws_envbuf);
+      ws->ws_envidx = ws->ws_envsiz = 0;
+      ws->ws_envbuf = NULL;
+    }
+}
+
+void
+mu_wordsplit_clearerr (struct mu_wordsplit *ws)
+{
+  if (ws->ws_errno == MU_WRDSE_USERERR)
+    free (ws->ws_usererr);
+  ws->ws_usererr = NULL;
+  ws->ws_errno = MU_WRDSE_OK;
+}
+
+void
 mu_wordsplit_free (struct mu_wordsplit *ws)
 {
   mu_wordsplit_free_words (ws);
   free (ws->ws_wordv);
   ws->ws_wordv = NULL;
+  mu_wordsplit_free_envbuf (ws);
 }
 
 void
-mu_wordsplit_perror (struct mu_wordsplit *wsp)
+mu_wordsplit_getwords (struct mu_wordsplit *ws, int *wordc, char ***wordv)
 {
-  switch (wsp->ws_errno)
-    {
-    case MU_WRDSE_EOF:
-      wsp->ws_error (_("no error"));
-      break;
-
-    case MU_WRDSE_QUOTE:
-      wsp->ws_error (_("missing closing %c (start near #%lu)"),
-                    wsp->ws_input[wsp->ws_endp],
-                    (unsigned long) wsp->ws_endp);
-      break;
-
-    case MU_WRDSE_NOSPACE:
-      wsp->ws_error (_("memory exhausted"));
-      break;
-
-    case MU_WRDSE_NOSUPP:
-      wsp->ws_error (_("command substitution is not yet supported"));
-
-    case MU_WRDSE_USAGE:
-      wsp->ws_error (_("invalid mu_wordsplit usage"));
-      break;
-
-    case MU_WRDSE_CBRACE:
-      wsp->ws_error (_("unbalanced curly brace"));
-      break;
-
-    case MU_WRDSE_UNDEF:
-      wsp->ws_error (_("undefined variable"));
-      break;
-
-    case MU_WRDSE_NOINPUT:
-      wsp->ws_error (_("input exhausted"));
-      break;
-      
-    default:
-      wsp->ws_error (_("unknown error"));
-    }
+  char **p = realloc (ws->ws_wordv,
+                     (ws->ws_wordc + 1) * sizeof (ws->ws_wordv[0]));
+  *wordv = p ? p : ws->ws_wordv;
+  *wordc = ws->ws_wordc;
+  ws->ws_wordv = NULL;
+  ws->ws_wordc = 0;
+  ws->ws_wordn = 0;
 }
 
 const char *_mu_wordsplit_errstr[] = {
   N_("no error"),
   N_("missing closing quote"),
   N_("memory exhausted"),
-  N_("command substitution is not yet supported"),
-  N_("invalid mu_wordsplit usage"),
+  N_("invalid wordsplit usage"),
   N_("unbalanced curly brace"),
   N_("undefined variable"),
-  N_("input exhausted")
+  N_("input exhausted"),
+  N_("unbalanced parenthesis"),
+  N_("globbing error")
 };
 int _mu_wordsplit_nerrs =
   sizeof (_mu_wordsplit_errstr) / sizeof (_mu_wordsplit_errstr[0]);
@@ -1616,7 +2311,26 @@ int _mu_wordsplit_nerrs =
 const char *
 mu_wordsplit_strerror (struct mu_wordsplit *ws)
 {
+  if (ws->ws_errno == MU_WRDSE_USERERR)
+    return ws->ws_usererr;
   if (ws->ws_errno < _mu_wordsplit_nerrs)
     return _mu_wordsplit_errstr[ws->ws_errno];
   return N_("unknown error");
 }
+
+void
+mu_wordsplit_perror (struct mu_wordsplit *wsp)
+{
+  switch (wsp->ws_errno)
+    {
+    case MU_WRDSE_QUOTE:
+      wsp->ws_error (_("missing closing %c (start near #%lu)"),
+                    wsp->ws_input[wsp->ws_endp],
+                    (unsigned long) wsp->ws_endp);
+      break;
+
+    default:
+      wsp->ws_error (mu_wordsplit_strerror (wsp));
+    }
+}
+
diff --git a/libmailutils/tests/modmesg.c b/libmailutils/tests/modmesg.c
index 5ea0a2a..bcd8a0a 100644
--- a/libmailutils/tests/modmesg.c
+++ b/libmailutils/tests/modmesg.c
@@ -32,7 +32,6 @@ main (int argc, char **argv)
   mu_stream_t stream = NULL;
   mu_header_t hdr;
   mu_body_t body;
-  char *buf = NULL;
   
   mu_set_program_name (argv[0]);
 
@@ -78,15 +77,20 @@ main (int argc, char **argv)
        }
       else if (strcmp (argv[i], "-t") == 0)
        {
-         size_t len;
+         mu_wordsplit_t ws;
          i++;
          assert (argv[i] != NULL);
-         len = strlen (argv[i]);
-         buf = realloc (buf, len + 1);
-         mu_wordsplit_c_unquote_copy (buf, argv[i], len);
-         assert (buf != NULL);
-         assert (mu_stream_write (stream, buf,
-                                  strlen (buf), NULL) == 0);
+
+         if (mu_wordsplit (argv[i], &ws,
+                           MU_WRDSF_NOSPLIT | MU_WRDSF_DEFFLAGS))
+           {
+             mu_error ("mu_wordsplit: %s", mu_wordsplit_strerror (&ws));
+             exit (1);
+           }
+         else
+           assert (mu_stream_write (stream, ws.ws_wordv[0],
+                                    strlen (ws.ws_wordv[0]), NULL) == 0);
+         mu_wordsplit_free (&ws);
        }
       else
        mu_error ("ignoring unknown argument %s", argv[i]);
diff --git a/libmailutils/tests/wordsplit.at b/libmailutils/tests/wordsplit.at
index 392da46..767fa24 100644
--- a/libmailutils/tests/wordsplit.at
+++ b/libmailutils/tests/wordsplit.at
@@ -347,7 +347,7 @@ TESTWSP([suppress ws trimming within quotes],[],
 4: "formatfield=In message %{text}, "
 ])
 
-TESTWSP([unescape],[],[-default novar nocmd quote escape '\"'],
+TESTWSP([unescape],[],[-default novar nocmd quote escape :+:'\\""'],
 [\Seen "quote \"" "bs \\"],
 [NF: 3
 0: \\Seen
diff --git a/libmailutils/tests/wsp.c b/libmailutils/tests/wsp.c
index c652107..28b7b36 100644
--- a/libmailutils/tests/wsp.c
+++ b/libmailutils/tests/wsp.c
@@ -22,6 +22,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <mailutils/wordsplit.h>
+#include <mailutils/alloc.h>
 #include <mailutils/kwd.h>
 #include <mailutils/errno.h>
 #include <mailutils/error.h>
@@ -29,6 +30,8 @@
 
 extern char **environ;
 
+char *progname;
+
 struct mu_kwd bool_keytab[] = {
   { "append", MU_WRDSF_APPEND },
   /*{ "reuse",  MU_WRDSF_REUSE },*/
@@ -50,6 +53,14 @@ struct mu_kwd bool_keytab[] = {
   { "default", MU_WRDSF_DEFFLAGS },
   { "env_kv", MU_WRDSF_ENV_KV },
   { "incremental", MU_WRDSF_INCREMENTAL },
+  { "pathexpand", MU_WRDSF_PATHEXPAND },
+  { NULL, 0 }
+};
+
+struct mu_kwd opt_keytab[] = {
+  { "nullglob", MU_WRDSO_NULLGLOB },
+  { "failglob", MU_WRDSO_FAILGLOB },
+  { "dotglob",  MU_WRDSO_DOTGLOB },
   { NULL, 0 }
 };
 
@@ -65,18 +76,27 @@ help ()
 {
   size_t i;
   
-  printf ("usage: wsp [options]\n");
+  printf ("usage: %s [options] [VAR=VALUE...]\n", progname);
   printf ("options are:\n");
   printf (" [-]trimnl\n");
   printf (" [-]plaintext\n");
+  printf (" -env\n");
+  printf (" env sys|none|null\n");
   putchar ('\n');
   for (i = 0; bool_keytab[i].name; i++)
     printf (" [-]%s\n", bool_keytab[i].name);
   putchar ('\n');
   for (i = 0; string_keytab[i].name; i++)
     {
-      printf (" -%s\n", bool_keytab[i].name);
-      printf (" %s ARG\n", bool_keytab[i].name);
+      printf (" -%s\n", string_keytab[i].name);
+      printf (" %s ARG\n", string_keytab[i].name);
+    }
+  printf (" escape-word ARG\n");
+  printf (" escape-quote ARG\n");
+  putchar ('\n');
+  for (i = 0; opt_keytab[i].name; i++)
+    {
+      printf (" [-]%s\n", opt_keytab[i].name);
     }
   putchar ('\n');
   printf (" -dooffs\n");
@@ -104,12 +124,7 @@ print_qword (const char *word, int plaintext)
   if (size >= qlen)
     {
       qlen = size + 1;
-      qbuf = realloc (qbuf, qlen);
-      if (!qbuf)
-       {
-         mu_error ("not enough memory");
-         abort ();
-       }
+      qbuf = mu_realloc (qbuf, qlen);
     }
   mu_wordsplit_c_quote_copy (qbuf, word, 0);
   qbuf[size] = 0;
@@ -131,50 +146,179 @@ make_env_kv ()
     ;
 
   size = (i - 1) * 2 + 1;
-  newenv = calloc (size, sizeof (newenv[0]));
-  if (!newenv)
-    {
-      mu_error ("not enough memory");
-      exit (1);
-    }
+  newenv = mu_calloc (size, sizeof (newenv[0]));
 
   for (i = j = 0; environ[i]; i++)
     {
       size_t len = strcspn (environ[i], "=");
-      char *p = malloc (len+1);
-      if (!p)
-       {
-         mu_error ("not enough memory");
-         exit (1);
-       }
+      char *p = mu_alloc (len+1);
       memcpy (p, environ[i], len);
       p[len] = 0;
       newenv[j++] = p;
-      p = strdup (environ[i] + len + 1);
-      if (!p)
-       {
-         mu_error ("not enough memory");
-         exit (1);
-       }
+      p = mu_strdup (environ[i] + len + 1);
       newenv[j++] = p;
     }
   newenv[j] = NULL;
   return newenv;
 }
-    
+
+static int
+wsp_getvar (char **ret, const char *vptr, size_t vlen, void *data)
+{
+  char **base = data;
+  int i;
+
+  for (i = 0; base[i]; i++)
+    {
+      size_t l = strcspn (base[i], "=");
+      if (l == vlen && memcmp (base[i], vptr, vlen) == 0)
+       {
+         char *p = strdup (base[i] + vlen + 1);
+         if (p == NULL)
+           return MU_WRDSE_NOSPACE;
+         *ret = p;
+         return MU_WRDSE_OK;
+       }
+    }
+  return MU_WRDSE_UNDEF;
+}
+
+static int
+wsp_runcmd (char **ret, const char *str, size_t len, char **argv, void 
*closure)
+{
+  FILE *fp;
+  char *cmd;
+  int c, lastc;
+  char *buffer = NULL;
+  size_t bufsize = 0;
+  size_t buflen = 0;
+  
+  cmd = malloc (len + 1);
+  if (!cmd)
+    return MU_WRDSE_NOSPACE;
+  memcpy (cmd, str, len);
+  cmd[len] = 0;
+
+  fp = popen(cmd, "r");
+  if (!fp)
+    {
+      size_t size = 0;
+      ret = NULL;
+      if (mu_asprintf (ret, &size, "can't run %s: %s",
+                      cmd, strerror (errno)))
+       return MU_WRDSE_NOSPACE;
+      else
+       return MU_WRDSE_USERERR;
+    }
+
+  while ((c = fgetc (fp)) != EOF)
+    {
+      lastc = c;
+      if (c == '\n')
+       c = ' ';
+      if (buflen == bufsize)
+       {
+         char *p;
+         
+         if (bufsize == 0)
+           bufsize = 80;
+         else
+           bufsize *= 2;
+         p = realloc (buffer, bufsize);
+         if (!p)
+           {
+             free (buffer);
+             free (cmd);
+             return MU_WRDSE_NOSPACE;
+           }
+         buffer = p;
+       }
+      buffer[buflen++] = c;
+    }
+
+  if (buffer)
+    {
+      if (lastc == '\n')
+       --buflen;
+      buffer[buflen] = 0;
+    }
+  
+  pclose (fp);
+  free (cmd);
+
+  *ret = buffer;
+  return MU_WRDSE_OK;
+}
+
+enum env_type
+  {
+    env_none,
+    env_null,
+    env_sys
+  };
+
+struct mu_kwd env_keytab[] = {
+  { "none",   env_none },
+  { "null",   env_null },
+  { "sys",    env_sys },
+  { NULL }
+};
+
+static void
+set_escape_string (mu_wordsplit_t *ws, int *wsflags, int q, const char *str)
+{
+  if (*str == ':')
+    {
+      while (*++str != ':')
+       {
+         int f;
+         switch (*str)
+           {
+           case '+':
+             f = MU_WRDSO_BSKEEP;
+             break;
+
+           case '0':
+             f = MU_WRDSO_OESC;
+             break;
+
+           case 'x':
+             f = MU_WRDSO_XESC;
+             break;
+
+           default:
+             fprintf (stderr, "%s: invalid escape flag near %s\n",
+                      progname, str);
+             abort ();
+           }
+         MU_WRDSO_ESC_SET (ws, q, f);
+       }
+      *wsflags |= MU_WRDSF_OPTIONS;
+      ++str;
+    }
+  ws->ws_escape[q] = str;
+}
+
 int
 main (int argc, char **argv)
 {
-  char buf[1024], *ptr;
+  char buf[1024], *ptr, *saved_ptr;
   int i, offarg = 0;
   int trimnl_option = 0;
   int plaintext_option = 0;
   int wsflags = (MU_WRDSF_DEFFLAGS & ~MU_WRDSF_NOVAR) |
                  MU_WRDSF_ENOMEMABRT |
-                 MU_WRDSF_ENV | MU_WRDSF_SHOWERR;
-  struct mu_wordsplit ws;
+                 MU_WRDSF_SHOWERR;
+  mu_wordsplit_t ws;
   int next_call = 0;
+  char *fenvbase[128];
+  size_t fenvidx = 0;
+  size_t fenvmax = sizeof (fenvbase) / sizeof (fenvbase[0]);
+  int use_env = env_sys;
+  
+  progname = argv[0];
 
+  ws.ws_options = 0;
   for (i = 1; i < argc; i++)
     {
       char *opt = argv[i];
@@ -212,7 +356,31 @@ main (int argc, char **argv)
          plaintext_option = !negate;
          continue;
        }
-    
+
+      if (strcmp (opt, "env") == 0)
+       {
+         if (negate)
+           use_env = env_none;
+         else
+           {
+             i++;
+             if (i == argc)
+               {
+                 fprintf (stderr, "%s: missing argument for env\n",
+                          progname);
+                 exit (1);
+               }
+
+             if (mu_kwd_xlat_name (env_keytab, argv[i], &use_env))
+               {
+                 fprintf (stderr, "%s: invalid argument for env\n",
+                          progname);
+                 exit (1);
+               }
+           }
+         continue;
+       }
+      
       if (mu_kwd_xlat_name (bool_keytab, opt, &flag) == 0)
        {
          if (negate)
@@ -231,7 +399,8 @@ main (int argc, char **argv)
              i++;
              if (i == argc)
                {
-                 mu_error ("%s missing argument", opt);
+                 fprintf (stderr, "%s: missing argument for %s\n",
+                          progname, opt);
                  exit (1);
                }
              
@@ -246,7 +415,8 @@ main (int argc, char **argv)
                  break;
 
                case MU_WRDSF_ESCAPE:
-                 ws.ws_escape = argv[i];
+                 set_escape_string (&ws, &wsflags, 0, argv[i]);
+                 set_escape_string (&ws, &wsflags, 1, argv[i]);
                  break;
                }
              
@@ -255,6 +425,27 @@ main (int argc, char **argv)
          continue;
        }
 
+      if (strcmp (opt, "escape-word") == 0
+         || strcmp (opt, "escape-quote") == 0)
+       {
+         int q = opt[7] == 'q';
+         
+         i++;
+         if (i == argc)
+           {
+             fprintf (stderr, "%s: missing argument for %s\n",
+                      progname, opt);
+             exit (1);
+           }
+         if (!(wsflags & MU_WRDSF_ESCAPE))
+           {
+             wsflags |= MU_WRDSF_ESCAPE;
+             ws.ws_escape[!q] = NULL;
+           }
+         set_escape_string (&ws, &wsflags, q, argv[i]);
+         continue;
+       }
+
       if (strcmp (opt, "dooffs") == 0)
        {
          if (negate)
@@ -267,20 +458,23 @@ main (int argc, char **argv)
              
              if (i == argc)
                {
-                 mu_error ("%s missing arguments", opt);
+                 fprintf (stderr, "%s: missing arguments for %s\n",
+                          progname, opt);
                  exit (1);
                }
              ws.ws_offs = strtoul (argv[i], &p, 10);
              if (*p)
                {
-                 mu_error ("invalid number: %s", argv[i]);
+                 fprintf (stderr, "%s: invalid number: %s\n",
+                          progname, argv[i]);
                  exit (1);
                }
 
              i++;
              if (i + ws.ws_offs > argc)
                {
-                 mu_error ("%s: not enough arguments", opt);
+                 fprintf (stderr, "%s: not enough arguments for %s\n",
+                          progname, opt);
                  exit (1);
                }
              offarg = i;
@@ -290,15 +484,65 @@ main (int argc, char **argv)
          continue;
        }
 
-      mu_error ("%s: unrecognized argument", opt);
+      if (mu_kwd_xlat_name (opt_keytab, opt, &flag) == 0)
+       {
+         wsflags |= MU_WRDSF_OPTIONS;
+         if (negate)
+           ws.ws_options &= ~flag;
+         else
+           ws.ws_options |= flag;
+         continue;
+       }
+      
+      if (strchr (opt, '='))
+       {
+         if (fenvidx < fenvmax - 1)
+           {
+             fenvbase[fenvidx++] = opt;
+             continue;
+           }
+         else
+           {
+             fprintf (stderr, "%s: environment too big\n", progname);
+             exit (1);
+           }
+       }
+      
+      fprintf (stderr, "%s: unrecognized argument: %s\n",
+              progname, opt);
       exit (1);
     }
 
-  if (wsflags & MU_WRDSF_ENV_KV)
-    ws.ws_env = (const char **) make_env_kv ();
-  else
-    ws.ws_env = (const char **) environ;
+  if (fenvidx)
+    {
+      fenvbase[fenvidx] = NULL;
+      wsflags |= MU_WRDSF_GETVAR | MU_WRDSF_CLOSURE;
+      ws.ws_getvar = wsp_getvar;
+      ws.ws_closure = fenvbase;
+    }
+
+  switch (use_env)
+    {
+    case env_null:
+      wsflags |= MU_WRDSF_ENV;
+      ws.ws_env = NULL;
+      break;
 
+    case env_none:
+      break;
+
+    case env_sys:
+      wsflags |= MU_WRDSF_ENV;
+      if (wsflags & MU_WRDSF_ENV_KV)
+       ws.ws_env = (const char **) make_env_kv ();
+      else
+       ws.ws_env = (const char **) environ;
+      break;
+    }
+  
+  if (!(wsflags & MU_WRDSF_NOCMD))
+    ws.ws_command = wsp_runcmd;
+  
   if (wsflags & MU_WRDSF_INCREMENTAL)
     trimnl_option = 1;
   
@@ -309,7 +553,11 @@ main (int argc, char **argv)
       size_t i;
       
       if (trimnl_option)
-       mu_rtrim_cset (ptr, "\n");
+       {
+         size_t len = strlen (ptr);
+         if (len && ptr[len-1] == '\n')
+           ptr[len-1] = 0;
+       }
       
       if (wsflags & MU_WRDSF_INCREMENTAL)
        {
@@ -318,16 +566,12 @@ main (int argc, char **argv)
              if (*ptr == 0)
                ptr = NULL;
              else
-               free ((void*)ws.ws_input);
+               free (saved_ptr);
            }
          else
            next_call = 1;
          if (ptr)
-           {
-             ptr = strdup (ptr);
-             if (!ptr)
-               abort ();
-           }
+           ptr = saved_ptr = mu_strdup (ptr);
        }
        
       rc = mu_wordsplit (ptr, &ws, wsflags);
@@ -344,8 +588,8 @@ main (int argc, char **argv)
            ws.ws_wordv[i] = argv[offarg + i];
          offarg = 0;
        }
-      
-      wsflags |= MU_WRDSF_REUSE;
+
+      wsflags |= MU_WRDSF_REUSE | (ws.ws_flags & MU_WRDSF_ENV);
       printf ("NF: %lu", (unsigned long) ws.ws_wordc);
       if (wsflags & MU_WRDSF_DOOFFS)
        printf (" (%lu)", (unsigned long) ws.ws_offs);
diff --git a/mu/shell.c b/mu/shell.c
index 8788717..539db56 100644
--- a/mu/shell.c
+++ b/mu/shell.c
@@ -252,12 +252,16 @@ shell_help (int argc, char **argv)
 static int
 shell_prompt (int argc, char **argv)
 {
-  size_t size;
+  mu_wordsplit_t ws;
   
-  free (mutool_shell_prompt);
-  size = strlen (argv[1]);
-  mutool_shell_prompt = mu_alloc (size + 1);
-  mu_wordsplit_c_unquote_copy (mutool_shell_prompt, argv[1], size);
+  if (mu_wordsplit (argv[1], &ws, MU_WRDSF_NOSPLIT | MU_WRDSF_DEFFLAGS))
+    mu_error ("mu_wordsplit: %s", mu_wordsplit_strerror (&ws));
+  else
+    {
+      free (mutool_shell_prompt);
+      mutool_shell_prompt = mu_strdup (ws.ws_wordv[0]);
+    }
+  mu_wordsplit_free (&ws);
   return 0;
 }
 
@@ -521,7 +525,7 @@ execute_line (char *line)
   int status = 0;
   
   ws.ws_comment = "#";
-  ws.ws_escape = "\\\"";
+  ws.ws_escape[0] = ws.ws_escape[1] = "\\\\\"\"";
   rc = mu_wordsplit (line, &ws,
                     MU_WRDSF_DEFFLAGS|MU_WRDSF_COMMENT|MU_WRDSF_ESCAPE|
                     MU_WRDSF_INCREMENTAL|MU_WRDSF_APPEND);


hooks/post-receive
-- 
GNU Mailutils



reply via email to

[Prev in Thread] Current Thread [Next in Thread]