/* 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 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* kActionTag = @"action"; static NSString* kDeleteAction = @"delete"; static NSString* kAddAction = @"add"; static BOOL ShouldAdd (NSDictionary* fileDictionary, BOOL includeDeletes); static BOOL ShouldAdd (NSDictionary* fileDictionary, BOOL includeDeletes) { NSString* valueString; BOOL addFile = YES; if ( !includeDeletes ) { valueString = [fileDictionary objectForKey:kHeadActionTag]; if ( [valueString isEqualTo:kDeleteAction] ) { valueString = [fileDictionary objectForKey:kHaveRevTag]; if ( !valueString ) { addFile = NO; } } } // don't include add files valueString = [fileDictionary objectForKey:kActionTag]; if ( valueString && [valueString isEqualTo:kAddAction] ) { 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