PerforceOutputParsers.mm #1

  • //
  • guest/
  • jeff_argast/
  • P4Cocoa/
  • source/
  • Perforce/
  • PerforceOutputParsers.mm
  • View
  • Commits
  • Open Download .zip Download (26 KB)
/*

Copyright (C) 2002-2003, Jeffrey D. Argast.

The authors make NO WARRANTY or representation, either express or implied,
with respect to this software, its quality, accuracy, merchantability, or
fitness for a particular purpose.  This software is provided "AS IS", and you,
its user, assume the entire risk as to its quality and accuracy.

Permission is hereby granted to use, copy, modify, and distribute this
software or portions thereof for any purpose, without fee, subject to these
conditions:

(1) If any part of the source code is distributed, then this
statement must be included, with this copyright and no-warranty notice
unaltered.

(2) Permission for use of this software is granted only if the user accepts
full responsibility for any undesirable consequences; the authors accept
NO LIABILITY for damages of any kind.

*/

#import "PerforceOutputParsers.h"

#import "AppUtils.h"
#import "PerforceDepot.h"
#import "PerforceDirectory.h"
#import "PerforceFile.h"
#import "PerforceFileRevision.h"
#import "PerforceChangeFile.h"
#import "PerforceChangeList.h"
#import "PerforceSubmittedChange.h"
#import "PerforceBranch.h"
#import "PerforceJob.h"
#import "PerforceLabel.h"
#import "PerforceClient.h"
#import "PerforceUser.h"

#include <string>

BOOL ScanJustPastChar (char** inCurPos, char* endPos, char scanChar);

static NSString* kEndOfLineToken = @"\n";
static NSString* kSpaceToken     = @" ";

@implementation PerforceOutputParsers

+ (NSArray*) parseDepotsOutput:(NSString*)dataString
{
	NSMutableArray* depotArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;
	
	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		// The first six characters should be 'Depot '
		const char* indata = [resultString cString] + 6;
		
		std::string depotName;
		
		while ( *indata != ' ' )
		{
			depotName += *indata;
			
			indata++;
		}
		
		// Get past the date
		indata++;
		
		while ( *indata++ != ' ' );
		
		int depotType = kLocalDepotType;
		
		if ( *indata == 'r' )
		{
			depotType = kRemoteDepotType;
		}
		
		[depotArray addObject:[[[PerforceDepot alloc] initWithName:depotName.c_str() withType:depotType] autorelease]];    
	
		[dataScanner scanString:kEndOfLineToken intoString:nil];
	}
	
	return depotArray;
}

+ (NSArray*) parseDirsOutput:(NSString*)dataString
{
	NSMutableArray* dirsArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;
	
	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		[dirsArray addObject:[[[PerforceDirectory alloc] initWithDir:[resultString cString]] autorelease]];
		
		[dataScanner scanString:kEndOfLineToken intoString:nil];
	}
	
	return dirsArray;
}
 
//
// fstat
//

static NSString* kDepotFileTag		= @"depotFile";
static NSString* kFstatPrefixTag	= @"... ";
static NSString* kIndentTag			= @"...";
static NSString* kHeadActionTag		= @"headAction";
static NSString* kHaveRevTag		= @"haveRev";
static NSString* kOtherOpenTag		= @"otherOpen";
static NSString* kOtherActionTag	= @"otherAction";

static NSString* kDeleteAction		= @"delete";

static BOOL ShouldAdd (NSDictionary* fileDictionary, BOOL includeDeletes);
static BOOL ShouldAdd (NSDictionary* fileDictionary, BOOL includeDeletes)
{
	BOOL addFile = YES;
	
	if ( !includeDeletes )
	{
		NSString* valueString = [fileDictionary objectForKey:kHeadActionTag];
		
		if ( [valueString isEqualTo:kDeleteAction] )
		{
			valueString = [fileDictionary objectForKey:kHaveRevTag];
		
			if ( !valueString )
			{
				addFile = NO;
			}
		}
	}
	
	return addFile;
}

static BOOL ParseLine (NSScanner* dataScanner, NSString** keyString, NSString** valueString);
static BOOL ParseLine (NSScanner* dataScanner, NSString** keyString, NSString** valueString)
{
	if ( [dataScanner isAtEnd] )
	{
		return NO;
	}
	
	if ( [dataScanner scanString:kFstatPrefixTag intoString:nil] == NO )
	{
		return NO;
	}
	
	if ( [dataScanner scanUpToString:kSpaceToken intoString:keyString] == NO )
	{
		return NO;
	}
	
	[dataScanner scanString:kSpaceToken intoString:nil];

	if ( [dataScanner scanUpToString:kEndOfLineToken intoString:valueString] == NO )
	{
		return NO;
	}
	
	[dataScanner scanString:kEndOfLineToken intoString:nil];
	
	return YES;
}

+ (NSArray*) parseFstatOutput:(NSString*)dataString includeDeletes:(BOOL)includeDeletes
{
	if ( [dataString length] < 1 )
	{
		return nil;
	}
	
	NSMutableArray* filesArray 	= [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner 		= [NSScanner scannerWithString:dataString];	
	
	NSString* keyString   		= nil;
	NSString* valueString 		= nil;
	NSString* currentDepotFile	= nil;
	
	NSMutableDictionary* fileDictionary = [[NSMutableDictionary alloc] init];
	NSArray* otherOpenArray				= nil;
	NSArray* otherActionArray			= nil;
	
	while ( ParseLine (dataScanner, &keyString, &valueString) )
	{
		if ( [keyString isEqualTo:kDepotFileTag] )
		{
			// This is only true for the very first depot file. After which
			// currentDepotFile is always valid
			if ( !currentDepotFile )
			{
				currentDepotFile = [valueString retain];
			}
			
			if ( ![currentDepotFile isEqualTo:valueString] )
			{
				if ( ShouldAdd (fileDictionary, includeDeletes) )
				{
					[filesArray addObject:[[[PerforceFile alloc] 
												initWithDict:fileDictionary
												otherOpen:otherOpenArray
												otherAction:otherActionArray] autorelease]];
				}
				
				[fileDictionary release];
				
				if ( otherOpenArray )
				{
					[otherOpenArray release];
					
					otherOpenArray = nil;
				}
				
				if ( otherActionArray )
				{
					[otherActionArray release];
					
					otherActionArray = nil;
				}
				
				fileDictionary = [[NSMutableDictionary alloc] init];
				
				[currentDepotFile release];
				
				currentDepotFile = [valueString retain];
			}
		}

		if ( [keyString isEqualTo:kIndentTag] )
		{
			NSMutableArray* targetArray = nil;
			NSString* otherString = nil;
			
			if ( !otherOpenArray )
			{
				otherOpenArray = [[NSMutableArray alloc] init];
			}
			
			if ( !otherActionArray )
			{
				otherActionArray = [[NSMutableArray alloc] init];
			}
			
			NSScanner* otherScanner = [NSScanner scannerWithString:valueString];
			
			if ( [otherScanner scanString:kOtherOpenTag intoString:nil] == YES )
			{
				targetArray = otherOpenArray;
			}
			else if ( [otherScanner scanString:kOtherActionTag intoString:nil] == YES )
			{
				targetArray = otherActionArray;
			}
			
			// if the next char is a space then we are at the last otherOpen line and
			// we can skip it
			[otherScanner scanInt:nil];
			
			if ( ![otherScanner isAtEnd] )
			{
				otherString = [valueString substringFromIndex:([otherScanner scanLocation] + 1)];
			
				[targetArray addObject:otherString];
			}
		}
		else
		{
			[fileDictionary setObject:valueString forKey:keyString];
		}
	}

	if ( fileDictionary )
	{
		if ( ShouldAdd (fileDictionary, includeDeletes) )
		{
			[filesArray addObject:[[[PerforceFile alloc] 
										initWithDict:fileDictionary
										otherOpen:otherOpenArray
										otherAction:otherActionArray] autorelease]];
		}
				
		[fileDictionary release];
		[otherOpenArray release];
		[otherActionArray release];
	}
	
	[currentDepotFile release];
	
	return filesArray;
}

	
//
//
//

+ (NSArray*) parseFilelogOutput:(NSString*)dataString
{
	NSMutableArray* revisionArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;
	NSString* fileName = nil;
	
	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		const char* data = [resultString cString];
		
		if ( *data != '.' )
		{	
			[fileName release];
			
			fileName = [resultString retain];
		}
		else
		{
			std::string fileRev, fileChangelist, fileDate, fileUser, fileAction, fileDescription;
			
			const char* indata = data + 4; // skip the ... 
			
			// skip branch history info
			if ( *indata == '#' )
			{
				indata++; // skip #
				
				// fileRev
				while ( *indata != ' ' )
				{
					fileRev += *indata;
					
					indata++;
				}
				
				// skip space
				indata++;
				
				//skip the word change
				while ( *indata++ != ' ' );
				
				// changelist
				while ( *indata != ' ' )
				{
					fileChangelist += *indata;
					
					indata++;
				}
				
				// skip space
				indata++;
				
				// action
				while ( *indata != ' ' )
				{
					fileAction += *indata;
					
					indata++;
				}
		
				// skip space
				indata++;
		
				//skip the word on
				while ( *indata++ != ' ' );
						
				// date
				while ( *indata != ' ' )
				{
					fileDate += *indata;
					
					indata++;
				}
				
				// skip space
				indata++;
		
				//skip the word by
				while ( *indata++ != ' ' );
						
				// user
				while ( *indata != ' ' )
				{
					fileUser += *indata;
					
					indata++;
				}
				
				// skip space
				indata++;
		
				//skip the file type
				while ( *indata++ != ' ' );
				
				// skip the '
				indata++;
				
				// desc
				while ( *indata != '\'' )
				{
					fileDescription += *indata;
					
					indata++;
				}
				
				[revisionArray addObject:[[[PerforceFileRevision alloc]   initWithRevision:fileRev.c_str()
																			withFileName:[fileName cString]
																			withChangeList:fileChangelist.c_str()
																			withDate:fileDate.c_str()
																			withUser:fileUser.c_str()
																			withAction:fileAction.c_str()
																			withDescription:fileDescription.c_str()]
																			autorelease]];
			}
		}
	}
	
	[fileName release];
	
	return revisionArray;
}

+ (NSArray*) parseOpenedOutput:(NSString*)dataString clientOnly:(BOOL)isClientOnly
{
	NSMutableArray* changesFileArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;

    NSString* userAtClient = GetUserAtClientString();
	
	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		char* data = (char*)[resultString cString];

		NSString*	fileName;
		NSString*	changeType;
		NSString*	changeName;
		NSString*	fileType;
		NSString*	userInfo = nil;
		int			revision;
		
		char* startPos 	= data;
		char* curPos 	= data;
		char* endPos	= data + [resultString length];

		// go until we get to a # sign
		if ( !ScanJustPastChar (&curPos, endPos, '#') )
			continue;
	
		// get the fileName
		fileName = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
	
		// go past the '#'
		startPos = curPos;
	
		// now go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
		
		// get the revision number
		revision = [[NSString stringWithCString:startPos length:(curPos - startPos - 1)] intValue];
	
		// skip the '- '
		curPos++;
		curPos++;
		
		startPos = curPos;
		
		// go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// the change type is next
		changeType = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
	
		startPos = curPos;
	
		// go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// The last word scanned is either change or default
		if ( *startPos == 'c' )
		{
			startPos = curPos;
		
			// go until we get to a space
			while ( *curPos++ != ' ' );
			
			// Get the change number
			changeName = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		}
		else
		{
			// The change name should be default in this case
			changeName = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		}
		
		// go until we get to a (
		if ( !ScanJustPastChar (&curPos, endPos, '(') )
			continue;
	
		startPos = curPos;
	
		// go until we get to a )
		if ( !ScanJustPastChar (&curPos, endPos, ')') )
			continue;
	
		// get the fileType
		fileType = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
	
		// When this is called without the -a option, the user name is left off
		if ( !isClientOnly )
		{
			// is there user info?
			if ( (*curPos != '\n') && (*curPos != 0) )
			{
				// skip the ' by '
				curPos += 4;
			
				startPos = curPos;
		
				// go until we get to the end
				while ( (*curPos != '\n') && (*curPos != 0) )
				{
					curPos++;
				}
		
				// get the fileType
				userInfo = [NSString stringWithCString:startPos length:(curPos - startPos)];
			}
		}
		else
		{
			userInfo = userAtClient;
		}
		
		[changesFileArray addObject:[[[PerforceChangeFile alloc] 
											initWithName:fileName
											withRevision:revision
											withFileType:fileType
											withChangeName:changeName
											withChangeType:changeType
											withUserInfo:userInfo
											isClient:(userInfo ? [userAtClient isEqualTo:userInfo] : YES)] autorelease]];
		
	}
	
    if ( [changesFileArray count] > 0 )
    {
        // Sort the changeFileArray by change name
        [changesFileArray sortUsingSelector:@selector(compare:)];
    }

	return changesFileArray;
}

//
//
//

+ (NSArray*) parseChangesPendingOutput:(NSString*)dataString
{
	NSMutableArray* changesArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;

    NSString* userAtClient = GetUserAtClientString();
	
	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		char* data = (char*)[resultString cString];

		NSString*	changeNumber;
		NSString*	userInfo;
		NSString*	changeDesc;
		
		char* startPos 	= data;
		char* curPos 	= data;
		char* endPos	= data + [resultString length];

		// now go until we get to a space, skipping 'Change '
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		startPos = curPos;
	
		// now go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the change number
		changeNumber = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// skip 'on '
		curPos += 3;
		
		startPos = curPos;
		
		// go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// skip 'by '
		curPos += 3;
	
		startPos = curPos;
	
		// go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// the user info is next
		userInfo = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		startPos = curPos;
	
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		// get the change desc
		changeDesc = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		[changesArray addObject:[[[PerforceChangeList alloc] 
											initWithNumber:changeNumber
											withUserInfo:userInfo
											withChangeDesc:changeDesc
											isClient:[userAtClient isEqualTo:userInfo]] autorelease]];
		
	}
	
    if ( [changesArray count] > 0 )
    {
        // Sort the changeFileArray by change name
        [changesArray sortUsingSelector:@selector(compare:)];
    }

	return changesArray;

}

//
//
//

+ (NSArray*) parseChangesSubmittedOutput:(NSString*)dataString
{
	NSMutableArray* changesArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;

	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		char* data = (char*)[resultString cString];

		NSString*	changeNumber;
		NSString*	changeDate;
		NSString*	userInfo;
		NSString*	changeDesc;
		
		char* startPos 	= data;
		char* curPos 	= data;
		char* endPos	= data + [resultString length];

		// now go until we get to a space, skipping 'Change '
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		startPos = curPos;
	
		// now go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the change number
		changeNumber = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// skip 'on '
		curPos += 3;
		
		startPos = curPos;
		
		// go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the change number
		changeDate = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// skip 'by '
		curPos += 3;
	
		startPos = curPos;
	
		// go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// the user info is next
		userInfo = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		startPos = curPos;
	
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		// get the change desc
		changeDesc = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		[changesArray addObject:[[[PerforceSubmittedChange alloc] 
											initWithNumber:changeNumber
											withDate:changeDate
											withUser:userInfo
											withDesc:changeDesc] autorelease]];
	}

	return changesArray;

}


//
//
//

+ (NSArray*) parseBranchesOutput:(NSString*)dataString
{
	NSMutableArray* branchesArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;

	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		char* data = (char*)[resultString cString];
	
		NSString*	branchName;
		NSString*	branchDate;
		NSString*	branchDesc;
		
		char* startPos 	= data;
		char* curPos 	= data;
		char* endPos	= data + [resultString length];

		// now go until we get to a space, skipping 'Branch '
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		startPos = curPos;
	
		// now go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the name
		branchName = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		startPos = curPos;
		
		// go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the date
		branchDate = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		startPos = curPos;
	
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		// get the change desc
		branchDesc = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		[branchesArray addObject:[[[PerforceBranch alloc] 
											initWithName:branchName
											withDate:branchDate
											withDesc:branchDesc] autorelease]];
		

	}

	return branchesArray;

}

+ (NSArray*) parseLabelsOutput:(NSString*)dataString
{
	NSMutableArray* labelsArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;

	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		char* data = (char*)[resultString cString];

		NSString*	labelName;
		NSString*	labelDate;
		NSString*	labelDesc;
		
		char* startPos 	= data;
		char* curPos 	= data;
		char* endPos	= data + [resultString length];

		// now go until we get to a space, skipping 'Label '
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		startPos = curPos;
	
		// now go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the name
		labelName = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		startPos = curPos;
		
		// go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the date
		labelDate = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		startPos = curPos;
	
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		// get the desc
		labelDesc = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		[labelsArray addObject:[[[PerforceLabel alloc] 
											initWithName:labelName
											withDate:labelDate
											withDesc:labelDesc] autorelease]];
		

	}

	return labelsArray;
}

+ (NSArray*) parseClientsOutput:(NSString*)dataString
{
	NSMutableArray* clientsArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;

	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		char* data = (char*) [resultString cString];

		NSString*	clientName;
		NSString*	clientDate;
		NSString*	clientRoot;
		NSString*	clientDesc;
		
		char* startPos 	= data;
		char* curPos 	= data;
		char* endPos	= data + [resultString length];

		// now go until we get to a space, skipping 'Client '
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		startPos = curPos;
	
		// now go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the name
		clientName = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		startPos = curPos;
		
		// go until we get to a space
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the date
		clientDate = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// skip 'root '
		curPos += 5;
		
		startPos = curPos;
	
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		// get root
		clientRoot = [NSString stringWithCString:startPos length:(curPos - startPos - 2)]; // -2 because we went past the space
	
		startPos = curPos;
	
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		// get the change desc
		clientDesc = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		[clientsArray addObject:[[[PerforceClient alloc] 
											initWithName:clientName
											withDate:clientDate
											withRoot:clientRoot
											withDesc:clientDesc] autorelease]];
		
	}

	return clientsArray;
}


+ (NSArray*) parseUsersOutput:(NSString*)dataString
{
	NSMutableArray* usersArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;

	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		char* data = (char*) [resultString cString];

		NSString*	userName;
		NSString*	userEmail;
		NSString*	userFullName;
		NSString*	userAccess;
		
		char* startPos 	= data;
		char* curPos 	= data;
		char* endPos	= data + [resultString length];
		
		startPos = curPos;
	
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the name
		userName = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// go until we get to a <
		if ( !ScanJustPastChar (&curPos, endPos, '<') )
			continue;
	
		startPos = curPos;
		
		// go until we get to a >
		if ( !ScanJustPastChar (&curPos, endPos, '>') )
			continue;
	
		// get the email
		userEmail = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// go until we get to a (
		if ( !ScanJustPastChar (&curPos, endPos, '(') )
			continue;
	
		startPos = curPos;
		
		// go until we get to a )
		if ( !ScanJustPastChar (&curPos, endPos, ')') )
			continue;
	
		// get the full name
		userFullName = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// skip ' accessed ' 
		curPos++;
		
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		startPos = curPos;
	
		// go until we get to the end
		while ( (*curPos != '\n') && (*curPos != 0) )
		{
			curPos++;
		}
	
		// get the access
		userAccess = [NSString stringWithCString:startPos length:(curPos - startPos)];
		
		[usersArray addObject:[[[PerforceUser alloc] 
											initWithName:userName
											withEmail:userEmail
											withFullName:userFullName
											withAccess:userAccess] autorelease]];
		
	}

	return usersArray;
}


+ (NSArray*) parseJobsOutput:(NSString*)dataString
{
	NSMutableArray* jobsArray = [[[NSMutableArray alloc] init] autorelease];
	
	NSScanner* dataScanner = [NSScanner scannerWithString:dataString];
	
	NSString* resultString = nil;

	while ( [dataScanner scanUpToString:kEndOfLineToken intoString:&resultString] == YES )
	{
		char* data = (char*) [resultString cString];

		NSString*	jobName;
		NSString*	jobStatus;
		NSString*	jobUser;
		NSString*	jobDate;
		NSString*	jobDesc;
		
		char* startPos 	= data;
		char* curPos 	= data;
		char* endPos	= data + [resultString length];
	
		startPos = curPos;
	
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
			
		// get the name
		jobName = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// skip 'on '
		curPos += 3;
		
		startPos = curPos;
		
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
		
		// get the date
		jobDate = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// skip 'by '
		curPos += 3;
		
		startPos = curPos;
		
		if ( !ScanJustPastChar (&curPos, endPos, ' ') )
			continue;
	
		// get the user
		jobUser = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		if ( !ScanJustPastChar (&curPos, endPos, '*') )
			continue;
			
		startPos = curPos;
	
		if ( !ScanJustPastChar (&curPos, endPos, '*') )
			continue;
	
		// get the change desc
		jobStatus = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		// go until we get to a '
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		startPos = curPos;
	
		if ( !ScanJustPastChar (&curPos, endPos, '\'') )
			continue;
	
		// get the change desc
		jobDesc = [NSString stringWithCString:startPos length:(curPos - startPos - 1)];
		
		[jobsArray addObject:[[[PerforceJob alloc] 
											initWithName:jobName
											withStatus:jobStatus
											withUser:jobUser
											withDate:jobDate
											withDesc:jobDesc] autorelease]];
	}

	return jobsArray;

}

BOOL ScanJustPastChar (char** inCurPos, char* endPos, char scanChar)
{
	char* curPos = *inCurPos;
	
	if ( curPos >= endPos )
		return NO;
		
	// go until we get to a space
	while ( (*curPos != scanChar) && (curPos < endPos) )
	{
		curPos++;
	}
	
	curPos++;

	// continue if we are at end of line
	if ( curPos > endPos )
		return NO;
		
	*inCurPos = curPos;

	return YES;
}
@end
# Change User Description Committed
#6 4208 Jeff Argast Added command line parsing and changed the sorting of user information
in the pending changes view
#5 3941 Jeff Argast Simplified fstat, branches, and job parsing.
#4 3936 Jeff Argast Integrated changes from Paul Ferguson's branch
Fixed parse error in PerforceUser
#3 3196 Jeff Argast Submitted Paul's changes to fix a hanging bug when a client root has a
non-ASCII character.  His changes also clean up the parsing of clients
tremendously.
#2 3113 Jeff Argast Reduced the times the depot view completely collapses.
Now it won't collapse on refresh views or submit, but
still collapses when the defaults change.
#1 2732 Jeff Argast Initial submission of P4Cocoa