wasCSharpSQLite – Rev 1

Subversion Repositories:
Rev:
/*
* GlobCmd.java
*
*       This file contains the Jacl implementation of the built-in Tcl "glob"
*       command.
*
* Copyright (c) 1997-1998 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and
* redistribution of this file, and for a DISCLAIMER OF ALL
* WARRANTIES.
* 
* Included in SQLite3 port to C# for use in testharness only;  2008 Noah B Hart
*
* RCS @(#) $Id: GlobCmd.java,v 1.5 1999/08/28 03:55:18 mo Exp $
*
*/
using System.IO;
using System.Text;

namespace tcl.lang
{

  /*
  * This class implements the built-in "glob" command in Tcl.
  */

  class GlobCmd : Command
  {

    /*
    * Special characters that are used for string matching. 
    */

    private static readonly char[] specCharArr = new char[] { '*', '[', ']', '?', '\\' };

    /*
    * Options to the glob command.
    */

    private static readonly string[] validOptions = new string[] { "-nocomplain", "--" };
    private const int OPT_NOCOMPLAIN = 0;
    private const int OPT_LAST = 1;

    public TCL.CompletionCode cmdProc( Interp interp, TclObject[] argv )
    {
      bool noComplain = false; // If false, error msg will be returned 
      int index; // index of the char just after the end 
      //   of the user name 
      int firstArg = 1; // index of the first non-switch arg 
      int i; // generic index 
      string arg; // generic arg string 
      string head = ""; // abs path of user name if provided 
      string tail = ""; // the remaining file path and pattern 
      TclObject resultList; // list of files that match the pattern

      for ( bool last = false; ( firstArg < argv.Length ) && ( !last ); firstArg++ )
      {


        if ( !argv[firstArg].ToString().StartsWith( "-" ) )
        {
          break;
        }
        int opt = TclIndex.get( interp, argv[firstArg], validOptions, "switch", 1 );
        switch ( opt )
        {

          case OPT_NOCOMPLAIN:
            noComplain = true;
            break;

          case OPT_LAST:
            last = true;
            break;

          default:
            throw new TclException( interp, "GlobCmd.cmdProc: bad option " + opt + " index to validOptions" );

        }
      }

      if ( firstArg >= argv.Length )
      {
        throw new TclNumArgsException( interp, 1, argv, "?switches? name ?name ...?" );
      }

      resultList = TclList.newInstance();
      resultList.preserve();

      for ( i = firstArg; i < argv.Length; i++ )
      {

        arg = argv[i].ToString();

        string separators; // The system-specific file separators
        switch ( JACL.PLATFORM )
        {

          case JACL.PLATFORM_WINDOWS:
            separators = "/\\:";
            break;

          case JACL.PLATFORM_MAC:
            if ( arg.IndexOf( (System.Char)':' ) == -1 )
            {
              separators = "/";
            }
            else
            {
              separators = ":";
            }
            break;

          default:
            separators = "/";
            break;

        }

        // Perform tilde substitution, if needed.

        index = 0;
        if ( arg.StartsWith( "~" ) )
        {
          // Find the first path separator after the tilde.

          for ( ; index < arg.Length; index++ )
          {
            char c = arg[index];
            if ( c == '\\' )
            {
              if ( separators.IndexOf( (System.Char)arg[index + 1] ) != -1 )
              {
                break;
              }
            }
            else if ( separators.IndexOf( (System.Char)c ) != -1 )
            {
              break;
            }
          }

          // Determine the home directory for the specified user.  Note 
          // that we don't allow special characters in the user name.

          if ( strpbrk( arg.Substring( 1, ( index ) - ( 1 ) ).ToCharArray(), specCharArr ) < 0 )
          {
            try
            {
              head = FileUtil.doTildeSubst( interp, arg.Substring( 1, ( index ) - ( 1 ) ) );
            }
            catch ( TclException e )
            {
              if ( noComplain )
              {
                head = null;
              }
              else
              {
                throw new TclException( interp, e.Message );
              }
            }
          }
          else
          {
            if ( !noComplain )
            {
              throw new TclException( interp, "globbing characters not supported in user names" );
            }
            head = null;
          }

          if ( (System.Object)head == null )
          {
            if ( noComplain )
            {
              interp.setResult( "" );
              return TCL.CompletionCode.RETURN;
            }
            else
            {
              return TCL.CompletionCode.RETURN;
            }
          }
          if ( index != arg.Length )
          {
            index++;
          }
        }

        tail = arg.Substring( index );

        try
        {
          doGlob( interp, separators, new StringBuilder( head ), tail, resultList );
        }
        catch ( TclException e )
        {
          if ( noComplain )
          {
            continue;
          }
          else
          {
            throw new TclException( interp, e.Message );
          }
        }
      }

      // If the list is empty and the nocomplain switch was not set then
      // generate and throw an exception.  Always release the TclList upon
      // completion.

      try
      {
        if ( ( TclList.getLength( interp, resultList ) == 0 ) && !noComplain )
        {
          string sep = "";
          StringBuilder ret = new StringBuilder();

          ret.Append( "no files matched glob pattern" );
          ret.Append( ( argv.Length == 2 ) ? " \"" : "s \"" );

          for ( i = firstArg; i < argv.Length; i++ )
          {

            ret.Append( sep + argv[i].ToString() );
            if ( i == firstArg )
            {
              sep = " ";
            }
          }
          ret.Append( "\"" );
          throw new TclException( interp, ret.ToString() );
        }
        else if ( TclList.getLength( interp, resultList ) > 0 )
        {
          interp.setResult( resultList );
        }
      }
      finally
      {
        resultList.release();
      }
      return TCL.CompletionCode.RETURN;
    }
    private static int SkipToChar( string str, int sIndex, char match )
    // Ccharacter to find.
    {
      int level, length, i;
      bool quoted = false;
      char c;

      level = 0;

      for ( i = sIndex, length = str.Length; i < length; i++ )
      {
        if ( quoted )
        {
          quoted = false;
          continue;
        }
        c = str[i];
        if ( ( level == 0 ) && ( c == match ) )
        {
          return i;
        }
        if ( c == '{' )
        {
          level++;
        }
        else if ( c == '}' )
        {
          level--;
        }
        else if ( c == '\\' )
        {
          quoted = true;
        }
      }
      return -1;
    }
    private static void doGlob( Interp interp, string separators, StringBuilder headBuf, string tail, TclObject resultList )
    {
      int count = 0; // Counts the number of leading file 
      //   spearators for the tail. 
      int pIndex; // Current index into tail 
      int tailIndex; // First char after initial file 
      //   separators of the tail 
      int tailLen = tail.Length; // Cache the length of the tail 
      int headLen = headBuf.Length; // Cache the length of the head 
      int baseLen; // Len of the substring from tailIndex
      //   to the current specChar []*?{}\\ 
      int openBraceIndex; // Index of the current open brace 
      int closeBraceIndex; // Index of the current closed brace 
      int firstSpecCharIndex; // Index of the FSC, if any 
      char lastChar = (char)( 0 ); // Used to see if last char is a file
      //   separator. 
      char ch; // Generic storage variable 
      bool quoted; // True if a char is '\\' 

      if ( headLen > 0 )
      {
        lastChar = headBuf[headLen - 1];
      }

      // Consume any leading directory separators, leaving tailIndex
      // just past the last initial separator.

      string name = tail;
      for ( tailIndex = 0; tailIndex < tailLen; tailIndex++ )
      {
        char c = tail[tailIndex];
        if ( ( c == '\\' ) && ( ( tailIndex + 1 ) < tailLen ) && ( separators.IndexOf( (System.Char)tail[tailIndex + 1] ) != -1 ) )
        {
          tailIndex++;
        }
        else if ( separators.IndexOf( (System.Char)c ) == -1 )
        {
          break;
        }
        count++;
      }

      // Deal with path separators.  On the Mac, we have to watch out
      // for multiple separators, since they are special in Mac-style
      // paths.

      switch ( JACL.PLATFORM )
      {

        case JACL.PLATFORM_MAC:

          if ( separators[0] == '/' )
          {
            if ( ( ( headLen == 0 ) && ( count == 0 ) ) || ( ( headLen > 0 ) && ( lastChar != ':' ) ) )
            {
              headBuf.Append( ":" );
            }
          }
          else
          {
            if ( count == 0 )
            {
              if ( ( headLen > 0 ) && ( lastChar != ':' ) )
              {
                headBuf.Append( ":" );
              }
            }
            else
            {
              if ( lastChar == ':' )
              {
                count--;
              }
              while ( count-- > 0 )
              {
                headBuf.Append( ":" );
              }
            }
          }
          break;


        case JACL.PLATFORM_WINDOWS:
          if ( name.StartsWith( ":" ) )
          {
            headBuf.Append( ":" );
            if ( count > 1 )
            {
              headBuf.Append( "/" );
            }
          }
          else if ( ( tailIndex < tailLen ) && ( ( ( headLen > 0 ) && ( separators.IndexOf( (System.Char)lastChar ) == -1 ) ) || ( ( headLen == 0 ) && ( count > 0 ) ) ) )
          {
            headBuf.Append( "/" );
            if ( ( headLen == 0 ) && ( count > 1 ) )
            {
              headBuf.Append( "/" );
            }
          }
          break;

        default:

          if ( ( tailIndex < tailLen ) && ( ( ( headLen > 0 ) && ( separators.IndexOf( (System.Char)lastChar ) == -1 ) ) || ( ( headLen == 0 ) && ( count > 0 ) ) ) )
          {
            headBuf.Append( "/" );
          }
          break;

      }

      // Look for the first matching pair of braces or the first
      // directory separator that is not inside a pair of braces.

      openBraceIndex = closeBraceIndex = -1;
      quoted = false;

      for ( pIndex = tailIndex; pIndex != tailLen; pIndex++ )
      {
        ch = tail[pIndex];
        if ( quoted )
        {
          quoted = false;
        }
        else if ( ch == '\\' )
        {
          quoted = true;
          if ( ( ( pIndex + 1 ) < tailLen ) && ( separators.IndexOf( (System.Char)tail[pIndex + 1] ) != -1 ) )
          {
            // Quoted directory separator. 

            break;
          }
        }
        else if ( separators.IndexOf( (System.Char)ch ) != -1 )
        {
          // Unquoted directory separator. 

          break;
        }
        else if ( ch == '{' )
        {
          openBraceIndex = pIndex;
          pIndex++;
          if ( ( closeBraceIndex = SkipToChar( tail, pIndex, '}' ) ) != -1 )
          {
            break;
          }
          throw new TclException( interp, "unmatched open-brace in file name" );
        }
        else if ( ch == '}' )
        {
          throw new TclException( interp, "unmatched close-brace in file name" );
        }
      }

      // Substitute the alternate patterns from the braces and recurse.

      if ( openBraceIndex != -1 )
      {
        int nextIndex;
        StringBuilder baseBuf = new StringBuilder();

        // For each element within in the outermost pair of braces,
        // append the element and the remainder to the fixed portion
        // before the first brace and recursively call doGlob.

        baseBuf.Append( tail.Substring( tailIndex, ( openBraceIndex ) - ( tailIndex ) ) );
        baseLen = baseBuf.Length;
        headLen = headBuf.Length;

        for ( pIndex = openBraceIndex; pIndex < closeBraceIndex; )
        {
          pIndex++;
          nextIndex = SkipToChar( tail, pIndex, ',' );
          if ( nextIndex == -1 || nextIndex > closeBraceIndex )
          {
            nextIndex = closeBraceIndex;
          }

          headBuf.Length = headLen;
          baseBuf.Length = baseLen;

          baseBuf.Append( tail.Substring( pIndex, ( nextIndex ) - ( pIndex ) ) );
          baseBuf.Append( tail.Substring( closeBraceIndex + 1 ) );

          pIndex = nextIndex;
          doGlob( interp, separators, headBuf, baseBuf.ToString(), resultList );
        }
        return;
      }

      // At this point, there are no more brace substitutions to perform on
      // this path component.  The variable p is pointing at a quoted or
      // unquoted directory separator or the end of the string.  So we need
      // to check for special globbing characters in the current pattern.
      // We avoid modifying tail if p is pointing at the end of the string.

      if ( pIndex < tailLen )
      {
        firstSpecCharIndex = strpbrk( tail.Substring( 0, ( pIndex ) - ( 0 ) ).ToCharArray(), specCharArr );
      }
      else
      {
        firstSpecCharIndex = strpbrk( tail.Substring( tailIndex ).ToCharArray(), specCharArr );
      }

      if ( firstSpecCharIndex != -1 )
      {
        // Look for matching files in the current directory.  matchFiles
        // may recursively call TclDoGlob.  For each file that matches,
        // it will add the match onto the interp.result, or call TclDoGlob
        // if there are more characters to be processed.

        matchFiles( interp, separators, headBuf.ToString(), tail.Substring( tailIndex ), ( pIndex - tailIndex ), resultList );
        return;
      }
      headBuf.Append( tail.Substring( tailIndex, ( pIndex ) - ( tailIndex ) ) );
      if ( pIndex < tailLen )
      {
        doGlob( interp, separators, headBuf, tail.Substring( pIndex ), resultList );
        return;
      }

      // There are no more wildcards in the pattern and no more unprocessed
      // characters in the tail, so now we can construct the path and verify
      // the existence of the file.

      string head;
      switch ( JACL.PLATFORM )
      {

        case JACL.PLATFORM_MAC:
          if ( headBuf.ToString().IndexOf( (System.Char)':' ) == -1 )
          {
            headBuf.Append( ":" );
          }
          head = headBuf.ToString();
          break;

        case JACL.PLATFORM_WINDOWS:
          if ( headBuf.Length == 0 )
          {
            if ( ( ( name.Length > 1 ) && ( name[0] == '\\' ) && ( ( name[1] == '/' ) || ( name[1] == '\\' ) ) ) || ( ( name.Length > 0 ) && ( name[0] == '/' ) ) )
            {
              headBuf.Append( "\\" );
            }
            else
            {
              headBuf.Append( "." );
            }
          }
          head = headBuf.ToString().Replace( '\\', '/' );
          break;

        default:
          if ( headBuf.Length == 0 )
          {
            if ( name.StartsWith( "\\/" ) || name.StartsWith( "/" ) )
            {
              headBuf.Append( "/" );
            }
            else
            {
              headBuf.Append( "." );
            }
          }
          head = headBuf.ToString();
          break;

      }
      addFileToResult( interp, head, separators, resultList );
    }
    private static void matchFiles( Interp interp, string separators, string dirName, string pattern, int pIndex, TclObject resultList )
    {
      bool matchHidden; // True if were matching hidden file 
      int patternEnd = pIndex; // Stores end index of the pattern 
      int dirLen = dirName.Length; // Caches the len of the dirName 
      int patLen = pattern.Length; // Caches the len of the pattern 
      string[] dirListing; // Listing of files in dirBuf 
      FileInfo dirObj; // File object of dirBuf 
      StringBuilder dirBuf = new StringBuilder();
      // Converts the dirName to string 
      //   buffer or initializes it with '.' 

      switch ( JACL.PLATFORM )
      {

        case JACL.PLATFORM_WINDOWS:

          if ( dirLen == 0 )
          {
            dirBuf.Append( "./" );
          }
          else
          {
            dirBuf.Append( dirName );
            char c = dirBuf[dirLen - 1];
            if ( ( ( c == ':' ) && ( dirLen == 2 ) ) || ( separators.IndexOf( (System.Char)c ) == -1 ) )
            {
              dirBuf.Append( "/" );
            }
          }

          // All comparisons should be case insensitive on Windows.

          pattern = pattern.ToLower();
          break;

        case JACL.PLATFORM_MAC:
        // Fall through to unix case--mac is not yet implemented.
        default:

          if ( dirLen == 0 )
          {
            dirBuf.Append( "." );
          }
          else
          {
            dirBuf.Append( dirName );
          }
          break;
      }

      dirObj = createAbsoluteFileObj( interp, dirBuf.ToString() );
      if ( !Directory.Exists( dirObj.FullName ) )
      {
        return;
      }

      // Check to see if the pattern needs to compare with hidden files.
      // Get a list of the directory's contents.

      if ( pattern.StartsWith( "." ) || pattern.StartsWith( "\\." ) )
      {
        matchHidden = true;
        // TODO tcl await only file names
        dirListing = addHiddenToDirList( dirObj );
      }
      else
      {
        matchHidden = false;
        DirectoryInfo dirInfo = new DirectoryInfo( dirObj.FullName );
        FileSystemInfo[] fileInfos = dirInfo.GetFileSystemInfos();
        // TCL await only file names
        // dirListing = Directory.GetFileSystemEntries(dirObj.FullName);
        dirListing = new string[fileInfos.Length];
        for ( int x = 0; x < fileInfos.Length; x++ )
        {
          dirListing[x] = fileInfos[x].Name;
        }
      }

      // Iterate over the directory's contents.

      if ( dirListing.Length == 0 )
      {
        // Strip off a trailing '/' if necessary, before reporting 
        // the error.

        if ( dirName.EndsWith( "/" ) )
        {
          dirName = dirName.Substring( 0, ( ( dirLen - 1 ) ) - ( 0 ) );
        }
      }

      // Clean up the end of the pattern and the tail pointer.  Leave
      // the tail pointing to the first character after the path 
      // separator following the pattern, or NULL.  Also, ensure that
      // the pattern is null-terminated.

      if ( ( pIndex < patLen ) && ( pattern[pIndex] == '\\' ) )
      {
        pIndex++;
      }
      if ( pIndex < ( patLen - 1 ) )
      {
        pIndex++;
      }

      for ( int i = 0; i < dirListing.Length; i++ )
      {
        // Don't match names starting with "." unless the "." is
        // present in the pattern.

        if ( !matchHidden && ( dirListing[i].StartsWith( "." ) ) )
        {
          continue;
        }

        // Now check to see if the file matches.  If there are more
        // characters to be processed, then ensure matching files are
        // directories before calling TclDoGlob. Otherwise, just add
        // the file to the resultList.

        string tmp = dirListing[i];
        if ( JACL.PLATFORM == JACL.PLATFORM_WINDOWS )
        {
          tmp = tmp.ToLower();
        }
        if ( Util.stringMatch( tmp, pattern.Substring( 0, ( patternEnd ) - ( 0 ) ) ) )
        {

          dirBuf.Length = dirLen;
          dirBuf.Append( dirListing[i] );
          if ( pIndex == pattern.Length )
          {
            addFileToResult( interp, dirBuf.ToString(), separators, resultList );
          }
          else
          {
            dirObj = createAbsoluteFileObj( interp, dirBuf.ToString() );
            if ( Directory.Exists( dirObj.FullName ) )
            {
              dirBuf.Append( "/" );
              doGlob( interp, separators, dirBuf, pattern.Substring( patternEnd + 1 ), resultList );
            }
          }
        }
      }
    }
    private static int strpbrk( char[] src, char[] matches )
    // The chars to search for in src.
    {
      for ( int i = 0; i < src.Length; i++ )
      {
        for ( int j = 0; j < matches.Length; j++ )
        {
          if ( src[i] == matches[j] )
          {
            return ( i );
          }
        }
      }
      return -1;
    }
    private static string[] addHiddenToDirList( FileInfo dirObj )
    // File object to list contents of
    {
      string[] dirListing; // Listing of files in dirObj
      string[] fullListing; // dirListing + .. and .
      int i, arrayLen;


      dirListing = Directory.GetFileSystemEntries( dirObj.FullName );
      arrayLen = ( (System.Array)dirListing ).Length;


      try
      {

        fullListing = (string[])System.Array.CreateInstance( System.Type.GetType( "java.lang.String" ), arrayLen + 2 );
      }
      catch ( System.Exception e )
      {
        return dirListing;
      }
      for ( i = 0; i < arrayLen; i++ )
      {
        fullListing[i] = dirListing[i];
      }
      fullListing[arrayLen] = ".";
      fullListing[arrayLen + 1] = "..";

      return fullListing;
    }
    private static void addFileToResult( Interp interp, string fileName, string separators, TclObject resultList )
    {
      string prettyFileName = fileName;
      int prettyLen = fileName.Length;

      // Java IO reuqires Windows volumes [A-Za-z]: to be followed by '\\'.

      if ( ( JACL.PLATFORM == JACL.PLATFORM_WINDOWS ) && ( prettyLen >= 2 ) && ( fileName[1] == ':' ) )
      {
        if ( prettyLen == 2 )
        {
          fileName = fileName + '\\';
        }
        else if ( fileName[2] != '\\' )
        {
          fileName = fileName.Substring( 0, ( 2 ) - ( 0 ) ) + '\\' + fileName.Substring( 2 );
        }
      }

      TclObject[] arrayObj = TclList.getElements( interp, FileUtil.splitAndTranslate( interp, fileName ) );
      fileName = FileUtil.joinPath( interp, arrayObj, 0, arrayObj.Length );

      FileInfo f;
      if ( FileUtil.getPathType( fileName ) == FileUtil.PATH_ABSOLUTE )
      {
        f = FileUtil.getNewFileObj( interp, fileName );
      }
      else
      {
        f = new FileInfo( interp.getWorkingDir().FullName + "\\" + fileName );
      }

      // If the last character is a spearator, make sure the file is an
      // existing directory, otherwise check that the file exists.

      if ( ( prettyLen > 0 ) && ( separators.IndexOf( (System.Char)prettyFileName[prettyLen - 1] ) != -1 ) )
      {
        if ( Directory.Exists( f.FullName ) )
        {
          TclList.append( interp, resultList, TclString.newInstance( prettyFileName ) );
        }
      }
      else
      {
        bool tmpBool;
        if ( File.Exists( f.FullName ) )
          tmpBool = true;
        else
          tmpBool = Directory.Exists( f.FullName );
        if ( tmpBool )
        {
          TclList.append( interp, resultList, TclString.newInstance( prettyFileName ) );
        }
      }
    }
    private static FileInfo createAbsoluteFileObj( Interp interp, string fileName )
    {
      if ( fileName.Equals( "" ) )
      {
        return ( interp.getWorkingDir() );
      }

      if ( ( JACL.PLATFORM == JACL.PLATFORM_WINDOWS ) && ( fileName.Length >= 2 ) && ( fileName[1] == ':' ) )
      {
        string tmp = null;
        if ( fileName.Length == 2 )
        {
          tmp = fileName.Substring( 0, ( 2 ) - ( 0 ) ) + '\\';
        }
        else if ( fileName[2] != '\\' )
        {
          tmp = fileName.Substring( 0, ( 2 ) - ( 0 ) ) + '\\' + fileName.Substring( 2 );
        }
        if ( (System.Object)tmp != null )
        {
          return FileUtil.getNewFileObj( interp, tmp );
        }
      }

      return FileUtil.getNewFileObj( interp, fileName );
    }
  } // end GlobCmd class
}