
WVPART(utils, The Utilities Library,

  WVPREFACE(Introduction,

    The "utils" library contains fundamental support utilities used throughout
    WvStreams, or at least ones that we couldn't classify anywhere else.

  )

  WVCHAPTER(basicutils, Basic String Handling,

  Here are some particularly simple C and C++ functions for manipulating
  strings.  Most of these only sit in C++ files is to make it easier to link
  with our C++ functions--they don't use many C++ features.  Functions that
  take or return WvStrings, however, are in C++.

  WVSECT1(strutils, String Utilities (strutils.cc),

    WVSECT2(backslashescape, backslash_escape(),
      WVCMD(WvString backslash_escape(WvStringParm s1))

      Returns a WvString with a backslash in front of every non-alphanumeric
      character in s1.
    )

    WVSECT2(encodehostnameasDN, encode_hostname_as_DN(),
      WVCMD(WvString encode_hostname_as_DN(WvStringParm hostname))

      Takes a hostname and splits it into parts.  For example, passing
      "www.fizzle.com" will return "dc=www,dc=fizzle,dc=com,cn=www.fizzle.com".
    )

    WVSECT2(getfilename, getfilename(),
      WVCMD(WvString getfilename(WvStringParm fullname))

      Returns the filename from a path.
      Together with getdirname(), separates the filename and directory name
      within a path.
    )

    WVSECT2(getdirname, getdirname(),
      WVCMD(WvString getfilename(WvStringParm fullname))

      Returns the directory name from a path.
    )

    WVSECT2(hexdumpbuffer, hexdump_buffer(),
      WVCMD(WvString hexdump_buffer(const void *_buf, size_t len))

      Produce a hexadecimal dump of the data buffer in 'buf' of length 'len'.
      It is formatted with 16 bytes per line; each line has an address offset,
      hex representation, and printable representation.

      This is used mostly for debugging purposes.  You can send the returned
      <link linkend="wvstring">WvString</link> object directly to a
      <link linkend="wvlog">WvLog</link> or any other <link linkend="wvstream">
      WvStream</link> for output.
    )

   WVSECT2(isnewline, isnewline(),
     WVCMD(bool isnewline(char c))

     Returns true if 'c' is a newline or carriage return character.  Increases
     code readability a bit.
   )

   WVSECT2(isword, is_word(),
     WVCMD(bool is_word(char *string))

     Returns true if all characters in 'string' are isalnum() (alphanumeric).
   )

   WVSECT2(lookup, lookup(),
     WVCMD(int lookup(const char *str, const char * const *table, bool case_sensitive = false);)

     Finds a string in an array and returns its index. Returns -1 if not found.
   )

   WVSECT2(nicehostname, nice_hostname(),
     WVCMD(WvString nice_hostname(WvStringParm name))

     Converts a string into a proper hostname, if possible.  It ensures that
     the hostname starts and ends with a letter or number, converts underscores
     to hyphens, and ensures that there aren't multiple hyphens in a row.

     If nice_hostname() can't convert name to a proper hostname, it returns
     the string "UNKNOWN".
   )

   WVSECT2(nonbreaking, non_breaking(),
     WVCMD(char *non_breaking(char * string))

     Replaces whitespace characters with nonbreaking spaces, for use with web
     stuff.

   )

   WVSECT2(replacechar, replace_char(),
     WVCMD(void replace_char(void *string, char c1, char c2, int length))

     Replace all instances of c1 with c2 for the first 'length' characters in
     'string'.  Ignores terminating NUL, so make sure you set 'length'
     correctly.
   )

   WVSECT2(rfc1123date, rf1123_date(),
     WVCMD(WvString rfc1123_date(time_t _when);)

     Returns an RFC1123-compatible date made out of _when
   )

   WVSECT2(rfc822date, rfc822_date(),
     WVCMD(WvString rfc822_date(time_t _when = -1))

     Returns an RFC822-compatible date ("Mon, Thu 18 71 14:55:12 EST") made
     from _when or, if _when < 0, from the current time.
   )

   WVSECT2(strcollsplit, strcoll_split(),
     WVCMD(void strcoll_split(StringCollection &coll, WvStringParm _s,
     const char *splitchars = " \t", int limit = 0))

     Splits a string and adds each substring to a collection.
     coll       : the collection of strings to add to
     _s         : the string to split
     splitchars : the set of delimiter characters
     limit      : the maximum number of elements to split
   )

   WVSECT2(strcolljoin, strcoll_join(),
     WVCMD(WvString strcoll_join(const StringCollection &coll,
     const char *joinchars = " \t"))

     Concatenates all strings in a collection and returns the result.
     * coll      : the collection of strings to read from
     * joinchars : the delimiter string to insert between strings
   )

   WVSECT2(strcount, strcount(),
     WVCMD(int strcount(WvStringParm s, const char c))

     Returns the number of occurrences of c in s.
   )

   WVSECT2(strlwr, strlwr(),
     WVCMD(char *strlwr(char *string))
    
     In-place modify a character string so that all contained letters are
     in lower case.  Returns 'string'.
   )

   WVSECT2(strreplace, strreplace(),
     WVCMD(WvString strreplace(WvStringParm s, WvStringParm a, WvStringParm
     b);)
 
     Replace any instances of 'a' with 'b' in 's'.
   )
   
   WVSECT2(strupr, strupr(),
     WVCMD(char *strupr(char *string))

     In-place modify a character string so that all contained letters are
     in upper case.  Returns 'string'.
   )
    
   WVSECT2(terminatestring, terminate_string(),
     WVCMD(char *terminate_string(char *string, char c))

     Add character c to the end of a string after removing terminating
     carriage returns/linefeeds if any.

     You need a buffer that's at least one character bigger than the
     current length of the string, including the terminating NUL.
   )
   
   WVSECT2(trimstring, trim_string(),
     WVCMD(char *trim_string(char *string))
    
     Trims whitespace from the beginning and end of the character string,
     including carriage return / linefeed characters.  Modifies the string
     in place.  Returns the new first character of the string, which points
     either at 'string' itself or some character contained therein.
    
     string is allowed to be NULL; returns NULL in that case.

     WVCMD(char *trim_string(char *string, char c))

     This is similar to the above, but in this case it trims off all characters
     starting at and including the first occurrence of c.
   )
    
   WVSECT2(webunescape, web_unescape(),
     WVCMD(WvString web_unescape(const char *str))

     Converts escaped characters from web URLs (such as "%20") into their normal
     ASCII representations.
   )
  )
  
  WVSECT1(verstring, Version String Manipulation (verstring.cc),

    These are version number and string manipulations, mostly specific to
    Net Integration software.

    Version numbers are 32-bit hexadecimal numbers such as 0x00012a00.  The
    first 16 bits are the major version, and the second 16 bits are the
    (fractional) minor version. For example, the above example corresponds to
    version "1.2a" (which is the version string).

    You can numerically compare version numbers using the standard C &lt; and
    &gt; operators, which is what makes them useful.

    Verstring cannot deal with version numbers that contain more than four
    digits to the left or right of the decimal, letters greater than f, or
    more than one decimal.


    WVSECT2(vertostring, ver_to_string(),
      WVCMD(const char *ver_to_string(unsigned int ver))

      Converts an integer, like 0x00012a00, to a string, like 1.2a.
    )

    WVSECT2(stringtover, string_to_ver(),
      WVCMD(unsigned int string_to_ver(const char *str))

      Converts a string, like 1.2a, to an integer, like 0x00012a00.
    )

  )

  WVSECT1(hex, Hexadecimal formating tools (WvHex.cc),
    WVSECT2(hexify, hexify(),
      WVCMD(void hexify(char *obuf, unsigned char *ibuf, size_t len))

      Write the contents of the binary string of length 'len' pointed to by
      'ibuf' into the output buffer 'obuf' in hexadecimal format.

      For example, if len==4, ibuf=="ABCDEF", then obuf will contain
      "41424344" with a terminating NUL character.

      This is useful to turn arbitrary binary into a simple printable format,
      so that it can (for example) be written to a <link
      linkend="wvconf">WvConf</link> configuration file.

      obuf must be a buffer with at least (len * 2) + 1 bytes available.
      (two digits for each byte of ibuf, plus a terminating NUL).

    )

    WVSECT2(unhexify, unhexify(),
      WVCMD(void unhexify(unsigned char *obuf, char *ibuf))

      Reverse the operation performed by <link
      linkend="hexify">hexify()</link>.  obuf must be a buffer large enough to
      contain the entire binary output string; you can calculate this size
      with (strlen(ibuf) / 2).  obuf will NOT be automatically NUL-terminated.

    )

  )

  WVSECT1(crypto, Crypto stuff (strcrypt.cc),
    This short section contains only passwd_crypt().

    WVSECT2(passwdcrypt, passwd_crypt(),
      WVCMD(WvString passwd_crypt(const char *str))

      Similar to the Linux system call crypt(), but this function selects its
      own salt.
    )

  )
 )

 WVCHAPTER(wvstring, WvString - dynamic character strings,

  WVSECT1(wvstringintro, Introduction,

   WvString is an implementation of a simple and efficient printable-string
   class.  It leaves out many of the notational conveniences provided by
   other string classes because they waste too much CPU time and space.

   It does the one thing really missing from char* strings, that is, dynamic
   buffer management.

   When you copy one WvString to another, it does _not_ duplicate the
   buffer; it just creates another pointer to it.  To really duplicate the
   buffer, call the unique() member function.

   However, if you assign a char* to a WvString, the entire string will be
   copied.  This is sometimes wasteful, but it helps avoid _a lot_ of bugs.
   If you don't want it copied (say, if you want a WvString as a
   function parameter and don't intend on modifying it), use a WvStringParm.
   WvStringParms are more-or-less the same as WvStrings, but they don't copy
   char*s, and they don't have an edit() function.

   To change the contents of a WvString, you need to run its edit() member
   function, which executes unique() and then returns a char* pointer to the
   WvString contents.  WvStrings will generally convert to const char*s when
   needed, but if you need to force a conversion (in case the function can't
   decide what type it wants, like printf()), use the cstr() member function.

   Be warned that some unscrupulous functions (such as strchr())
   return char*s even though they take const char*s, so it is possible to
   edit a non-unique WvString (and hence the original WvString).  These cases
   are pretty rare, but they occur.

  )

  WVSECT1(wvstringexamples, WvString Examples,

   WVCMD(WvString x("fuzzy wazoo");)

   In this case, a WvString object 'x' is created.  "fuzzy wazoo" is a
   static string, meaning a const char * is passed to the constructor of
   'x', so a dynamic string is created inside of 'x', and "fuzzy wazoo" is
   copied over.  No call to unique() is necessary in this case.

   When 'x' is destroyed (automatically upon exiting the C++ code block)
   the dynamic string will be deleted automatically.

   WVCMD(WvString output("fuzzy %-10.3s %5s\n", "abcdef", 12);
WvString c(output);
c.edit()[1] = 'a';)

   The first command above creates a WvString called 'output' which contains
   the string "fuzzy abc[seven spaces] [three spaces]12\n".  This uses the
   printf-like string formatting feature built into WvString.  Note that
   unlike printf, WvString's formatter is type-safe: you can't pass the
   wrong kind of object to the formatter.  In fact, everything you format
   must use a '%s' format string - %d, %f, etc are not supported.

   The above function call works as follows:  new WvStrings are allocated
   for the first two parameters ("fuzzy..." and "abcdef").  Then it turns
   '12' into a WvString using the WvString::WvString(int) constructor.
   All three operations require dynamic memory allocations.

   C++ then passes the new WvStrings on to the WvString "complex"
   constructor, which formats them according to the first string and
   generates an output string, which is always dynamically allocated.

   The second line above creates a WvString 'c' which is the same as
   'output'.  It does not cause any new dynamic memory allocations to occur
   and is very quick.

   The third line first makes 'c' unique (so that it has a separate copy
   of the output string), then changes the second character to 'a', so that
   now 'c' contains "fazzy abc 12" and output contains "fuzzy abc 12".

   WVCMD(WvString nul;)

   This command creates a null WvString.  This is NOT simply a WvString
   containing a zero-length string, but a WvString that points to nothing.
   You very seldom want to leave a WvString this way, but if you do, you
   can test for this condition simply by comparing it to NULL:

   WVCMD((nul == NULL))

   A zero-length WvString differs slightly from a null WvString.  You can
   declare one as follows:

   WVCMD(WvString zerolen("");)

   The following expression will be true:

   WVCMD((zerolen == ""))

   However, 'zerolen' will not be equal to NULL.  Conversely, a null
   WvString is not equal to the zero-length string, "".

   Most often, you will want to immediately fill a null WvString with
   an empty buffer with setsize(), as below.

   WVCMD(WvString newstr;
newstr.setsize(128);
sprintf(newstr.edit(), "blah blah %5.4f", floatval);)

   These commands first create a NULL WvString, then attach it to an
   uninitialized 128-byte buffer.  We then use sprintf to fill the string
   buffer.

  )

  WVSECT1(wvstringtable, WvStringTable,
    WVSECT2(wvstringtablejoin, join ,
      WVCMD(WvString WvStringTable::join(const char *joinchars) const)
      returns a string that join all the elements in WvStringTable by the specified delimiter.
      If no delimiter is specified, it uses " \t" by default.
    )

    WVSECT2(wvstringtablesplit, split,
      WVCMD(void WvStringTable::split(WvStringParm s, const char *splitchars,int limit))
      adds an element to the table.

      Another way to do this is by making use of the fact that WvStringTable is really a WvHashTable,
      so we can do add() and append().
      The difference is that add and append takes a reference instead of the constant string.

      Refer to WvHashTable for more details.

      Let's look at an example.

      WVEXAMPLE(wvstringtableex.cc)
   )
  )

  WVSECT1(wvstringlist, WvStringList ,

   This is almost the same as WvStringTable. All the functions available for
   WvStringTable is available here.

   WVSECT2(wvstringlistjoin, join,
    WVCMD(WvString WvStringList::join(const char *joinchars) const)
   )

   WVSECT2(wvstringlistsplit, split,
    WVCMD(void WvStringList::split(WvStringParm s, const char *splitchars, int limit))

    the same as WvStringTable.
    In addition, we have:
   )

   WVSECT2(wvstringlistfill, fill,
    WVCMD(void WvStringList::fill(const char * const *array))
    which appends the array to the list.
   )
   WVSECT2(wvstringlistpopstr, popstr,
    WVCMD(WvString WvStringList::popstr())
    which get the first string in the list, or an empty string if the list is empty
    and removes the returned string from the list.

    WVEXAMPLE(wvstringlistex.cc)
   )

  )

 )


 WVCHAPTER(wvlinklist, WvLinkList - type-safe linked lists and iterators,

   WvLinkList allows you to create type-safe lists of objects, along with
   appropriate iterators.  Lists are used all over the Weaver and WvStreams,
   and the best way to learn about them is by example--so read the source
   code and look at the sample program in testlist.cc.

   WVSECT1(declarewvlinklist, How to create a WvLinkList?,
     You can create a WvLinkList of any data type you want.
     An example:
     WCVCMD(DeclareWvList(WvString);)
     This will create a linked list that will contain data of WvString type.

     To use it:
     WVCMD(WvStringList l;)

     To fill the list, you may use

     WVCMD(l.append(address of data, false);)
     which appends the element to the end of the list, or
     WVCMD(l.add(address of data, false);)
     which is exactly the same as append, or
     WVCMD(l.prepend(address of data, false);)
     which prepends the element to the beginning of the list.

     The false as the second argument is "autofree". If it is true, the list
     takes ownership of the element.

     The actual prototype of these functions are:
     WVCMD(void append(T *data, bool autofree, char *id = NULL)
void add(T *data, bool autofree, char *id = NULL)
void prepend(T *data, bool autofree, char *id = NULL))

     To quickly determines if the list is empty,
     WVCMD(t.isempty();)
     returns true if empty.

     You can also count the number of words in the list with:
     WVCMD(l.count();)

     We also have an iterator through the list, which is declare as follows
     WVCMD(WvStringList::Iter i(l);)
     Before using, it must be reset using rewind().


     WVCMD(i.rewind())
     rewinds the iterator so that it points to the first element of the list.

     WVCMD(i.next())
     moves the iterator along the list to point to the next element, returns:
     the current WvLink pointer, or null if there were no more elements
     remaining in the traversal sequence

     WCVCMD(l.cur())
     returns a pointer to the WvLink at the iterator's current location.

     WVCMD(l.find(const void *data))
     rewinds the iterator and repositions it over the element that
     matches the specified value, and returns the current WvLink pointer, or
     null if no such element was found

     WVCMD(l.zap())
     destroys the list.

     WVCMD(l.zap(true))
     destroys any elements that were added with autofree == true.

     WVCMD(l.first())
     returns a pointer to the first element in the linked list, possibly null
     if it is empty.

     WVCMD(l.last())
     returns a pointer to the last element in the linked list, possibly null
     if it is empty.

     WVCMD(l.add_after(WvLink *after, T *data, bool autofree, char *id = NULL ))
     adds the element after the specified link in the list.

     - "link" is the link preceeding the desired location of the element
        to be inserted, non-null
     - "data" is the element pointer, may be null
     - "autofree" is if true, takes ownership of the element
     - "id" is an optional string to associate with the element, or null

     WVCMD(l.unlink(address to data))
     unlinks the specified element from the list.

     WVCMD(l.unlink_first())
     unlinks the first element from the list.

     WVCMD(l.unlink_after(WvLink *after, bool destroy = true))
     unlinks the element that follows the specified link in the list.


     You might also want to read the top of wvlinklist.h for some implementation
     details.

     A typical use of WvLinkList would be something like this:

     WVEXAMPLE(wvlistex.cc)

     The result is:

     WVCMD( blah blah
bork bork
Is the list empty? No
The first element is: blah blah
The last element is: bork bork)

     Here is another example, with a list of integers.
     WVEXAMPLE(intlistex.cc)


     Some rather horrible macros are used to declare actual concrete list types.
     List type construction is facilitated by the following macros:

     - DeclareWvList(Type): creates a subclass named WvListType
       that contains pointers to Type.
     - DeclareWvList2(name, Type): as the above, but calls the
       resulting class by the specified name.

   )
 )

 WVCHAPTER(wvhashtable, WvHashTable - type-safe hash tables and iterators,

   WvHashTable works a lot like <link linkend="wvlinklist">WvLinkList</link>,
   except it allows for fast indexing of objects in the table (or
   "dictionary") using the [] operator.

   We implement a hash table as a fixed-size array of WvLinkLists.  Someday,
   we might change the implementation to use a self-resizing array instead.

   Iterators work with WvHashTable in exactly the same way as with
   WvLinkList.

   WvHashTable usage is described more fully, along with examples, in
   wvhashtable.h.

   WVSECT1(declarewvtable, How to create a WvHashTable?,
      You can create a WvHashTable of any data type you want. For example,
      a WvHashTable of WvString data type can be create like this:

      WVCMD(DeclareWvTable(WvString);)

      This will create a hashtable that will contain data of WvString type.
      It actually instantiates a WvStringTable class that inherits from
      a WvHashTable class that handles WvString data types.
      Or you can just use WvStringTable.h.

      You can set the size of your dictionary by invoking the constructor of
      the WvStringTable class, like this:

      WVCMD(WvStringTable t(100);)
      The WvString hashtable now has 100 slots.

      You can create some data of type WvString and put them in the dictionary like this:

      WVCMD(t.add(address of data, false);)
      Note that you have to pass the address of data as the first argument,
      and not the data itself.
      Here the second parameter 'false' means that you don't want the data to be
      automatically destroyed at the end of the program. This is used because some
      data types like WvString already has the automatic self-destroy feature.
      For some other data types, you may need to set the second parameter to 'true'.

      You may also use
      WVCMD(t.append(address of data, false);)
      or

      WVCMD(t.prepend(address of data, false);)
      to add to the dictionary.

      You can use the iterator to go through the hashtable and print it out if you like.
      WVCMD(WvStringTable::Iter i(t);)

      You can also count the number of words in the dictionary with:
      WVCMD(t.count();)

      To find out if a word is already in the dictionary, you can invoke the comparison by
      using the [] operator. For example, you want to know if the word 'bijoux' exists
      in the dictionary. So you declare a WvString variable that contains the string
      'bijoux', like this:

      WVCMD(WvString sample("bijoux");)

      Then you can compare it like this:

      t[sample] returns NULL if 'bijoux' is NOT in the dictionary.
      t[sample] returns the word that matches 'bijoux' in the dictionary if 'bijoux is in the dictionary.

      To remove a word from the dictionary, do this:

      WVCMD(t.remove(address of data);)

      To empty the entire dictionary, do this:

      WVCMD(t.zap();)

      You may want to do an iterator with a sorted list, since it is a linked list.  Here is how
      WVCMD(WvStringTable::Sorter s(t,);)

      The following example shows all the mentioned features of a hashtable.
      Please take a look and try compile and run it to see for yourself.

      WVEXAMPLE(wvhashtableex.cc)

      The result is:
      WVCMD(What words do we have in the dictionary?
matin
oui
Bonjour
bonsoir
demain
comment
non
aussi
depanneur
bien
There are 10 words stored in the dictionary so far.
Is 'Bonjour' in the dictionary? Yes
Is 'Salut' in the dictionary? No
Modified List:
matin
oui
Bonjour
bonsoir
demain
comment
non
depanneur
bien
Sorted modified List:
bien
Bonjour
bonsoir
comment
demain
depanneur
matin
non
oui
Empty List:)


   )

 )

 WVCHAPTER(wvbuffer, WvBuffer - dynamically-resizing binary buffers,

   A WvBuffer is a dynamically-sized buffer.  It's often perfect for storing
   streamed data.

   WvBuffer is pretty simple to use.  You can write data to it using put()
   and retrieve the data using get().  get() effectively deletes the data
   from the buffer, but you can use unget() to get it back if you haven't
   done any buffer operations in the meantime.

   WVCMD(WvBuffer buf;
buf.put("borkle borkle\n");
printf(buf.get(14));
buf.unget(14);
printf(buf.get(14));)

   The above code will print "borkle borkle" twice.

   WVSECT1(wvbuffercc,  WvBuffer (WvBuffer.cc),
     WVSECT2(alloc, alloc(),

       WVCMD(unsigned char *alloc(size_t num))

       allocates 'num' bytes in the buffer and returns a pointer to its start
       . Pointer is valid until next zap() or get().
     )

     WVSECT2(unalloc, unalloc(),

       WVCMD(void unalloc(size_t num))

       unallocates the last 'num' bytes in the buffer that were previously al
       located using alloc() or put(), They are then available for a subseque       nt alloc() or put().
     )
  
     WVSECT2(put, put(),

       WVCMD(void put(WvStringParm str))

       Copy a WvString into the buffer, not including the terminating nul.

       WVCMD(void put(const void *data, size_t num))

       Copy 'buf' into the next 'num' bytes of buffer.
    )

     WVSECT2(get, get(),
     
       WVCMD(unsigned char *get(size_t num))

       Return the next 'num' bytes in the buffer. Returns NULL if there are not enough bytes in the buffer.
     )

     WVSECT2(unget, unget(),
     
       WVCMD(void unget(size_t num))

       Undo all or part of the previous get(). You can unget() up to the numb
       er of bytes you did in the last get(), assuming you have not done any        other buffer operation in the meantime.
     )

     WVSECT2(match, match(),

       WVCMD(size_t match(const unsigned char chlist[], size_t numch, bool reverse = false))

       Returns the number of leading bytes that match any in chlist. If rever
       se == trye, match bytes that are not in chlist.
     )

     WVSECT2(merge, merge(),
  
       WVCMD(void merge(WvBuffer &buf))

       Merges another WvBuffer into this one, simply move all of its objects into our own list.
    )
   )

   WVSECT1(wvbufferstore, WvBufferStore - basic buffer storage class,
   x
   )
 )

 WVCHAPTER(wvdiriter, WvDirIter,
   WvDirIter is a directory iterator. It basically implements WVCMD(find).

   WVCMD(WvDirIter( WvString dirname, bool _recurse=true );)
   where dirname is the directory name.

   A simple example:
   WVEXAMPLE(wvdiriterex.cc)

 )

 WVCHAPTER(wvencoder, WvEncoder - A top-level data encoder class,
   The base encoder class.

   Encoders read data from an input buffer, transform it in some way, then
   write the results to an output buffer. The resulting data may be of a
   different size or data type, and may or may not depend on previous data.



   WVSECT1(wvbase64, An encoding tool (WvBase64.cc),

    Contains functions for encoding and decoding strings in MIME Base64 notation.

    WVSECT2(wvbase64encode, WvBase64Encoder(),
     Converts a string to base64 encoding.
    )

    WVSECT2(wvbase64decode, WvBase64Decoder(),
     Converts a string from base64 format into a normal string format.

     An example of WvBase64Encoder:
     WVEXAMPLE(wvbase64ex.cc)
    )
   )

  WVSECT1(wvbackslash, Another encoding tool (WvBackSlash.cc),
   You almost never use this, but it's there if you need it.
   WvBackSlash performs C-style backslash escaping and unescaping of strings.

   WVEXAMPLE(backslashex.cc)

  )
 )


 WVCHAPTER(wvcrash, WvCrash - automatic stack backtrace generation,

   WvCrash is a simple way to get useful information when a program dies.
   It automatically generates a stack trace, including names of not only your
   program's functions but also library functions.

   Setting up WvCrash involves running wvcrash_setup() and passing it the name
   of your program (generally you'll want to pass argv[0]).  When the
   program dies, it automatically executes the user-defined program "wvcrash",
   if it exists, and pipes the WvCrash stack trace to its stdin.  If there's no
   program called "wvcrash" in the path, it writes the stack trace to a file
   named "wvcrash.txt" in the current directory.

 )

 WVCHAPTER(wvtclstring, WvTclString,
  x
 )

)
