This is MiscShell.m in view mode; [Download] [Up]
#import <MiscShell.h> /* * $Header: /SAHayman/LocalDeveloper/Source/MiscKit/Palettes/MiscShell/MiscShell.subproj/RCS/MiscShell.m,v 1.2 94/08/04 17:39:39 shayman Exp Locker: shayman $ * $Log: MiscShell.m,v $ * Revision 1.2 94/08/04 17:39:39 shayman * Fiddling with string values * */ /* * Note: * In several spots we do things like * if( [aVar isKindOfClassNamed:"DBTableView"] ) * instead of * if ( [avar isKindOf:[DBTableView class]] ) * * This is because you can do the first test without having to link * DBKit into your program. Doing the second requires linking DBKit, * including the DBKit header files in this program, etc etc, which * we'd rather avoid if we can. Same for NXTableView. Since I want this * object to be usable with either, this seems like the best way to * avoid various linking problems. * * I have done the same thing with other AppKit classes, e.g. * if ( [aVar isKindOfClassNamed:"Matrix"] ) * for consistency. */ #define MISC_SHELL_VERSION 3 @interface MiscShell(PrivateMethods) - setScriptFromCString:(const char *)str; - sendText:(const char *)buffer andNewline:(BOOL)newline to:text; - setupEnvironment:process forSender:sender; - addEnvVar:(const char *)varName value:(const char *)val to:env; - handleCompleteLine; - startOutput; - finishOutput; - resort:(int)keyField; - field:(int)n ofString:s; - splitupCurrentLine; - (BOOL)useCustomDelimiters; - (int) fieldsInString:(MiscString *)str; @end @implementation MiscShell(PrivateMethods) - (BOOL)useCustomDelimiters { return [[self customDelimiters] length] > 0; } /* * Add a bunch of environment variables to a process. */ - setupEnvironment:proc forSender:sender { NXBundle *mainBundle = [NXBundle mainBundle]; id env = [proc environment]; // a MiscStringArray int i; char varName[3]; id v; /* * Go through each of our v1...v9 instance variables in turn by * making use of this clever object_getInstanceVariable runtime function. */ for ( i = 1; i <= 9; i++ ) { sprintf(varName, "v%d", i); // See if we have an instance variable by that name .. if ( object_getInstanceVariable(self, varName, (void *)&v) ) { // Now see if that obj responds to stringValue:, and if so, // add an environment var representing its current stringValue. // Special case: if the object is a browser, we write // out the value of all its selected cells, separated // by tabs. Rather than just the stringValue. if ( [v isKindOfClassNamed:"NXBrowser"] ) { List *selections = [[List alloc] init]; MiscString *str = [[MiscString alloc] init]; int i; id aCell; [v getSelectedCells:selections]; i = 0; while ( aCell = [selections objectAt:i++] ) { [str catFromFormat:"%s", [aCell stringValue]]; if ( i < [selections count] ) [str catFromFormat:"\t"]; } [self addEnvVar:varName value:[str stringValue] to:env]; [selections free]; [str free]; } else if ( [v respondsTo:@selector(stringValue)] ) [self addEnvVar:varName value:[v stringValue] to:env]; } } // Add a var called "sender" containing the sender's string value // (if known.) // TODO - should we be using [sender selectedCell] here if it's // a Matrix sending us this message? if ( [sender respondsTo:@selector(stringValue)] ) [self addEnvVar:"sender" value:(const char *)[sender stringValue] to:env]; // What the hell, add a var called "senderTag" too. { char tag[10]; if ( [sender respondsTo:@selector(tag)] ) { sprintf(tag, "%d", [sender tag]); [self addEnvVar:"senderTag" value:tag to:env]; } } // TODO // Maybe if it's a Form sending us the message, we add a bunch // of variables for each entry in the Form. // Add a var called "mainBundle" giving our main application // directory. [self addEnvVar:"mainBundle" value:[mainBundle directory] to:env]; return self; } // Add an environment variable to a MiscStringArray, which belongs to // the subprocess - addEnvVar:(const char *)varName value:(const char *)value to:env { MiscString *newStr = [[MiscString alloc] init]; if ( ! value ) return nil; [newStr catFromFormat:"%s=%s", varName, value]; [env addString:[newStr stringValue]]; [newStr free]; return self; } // Internal method, set our script from a (char *) - setScriptFromCString:(const char *)str { if ( script ) [script free]; script = [MiscString newWithString:str]; return self; } // Internal method that passes some text along to either a // scrolling text object (appended), or something responding to setStringValue. // it's appended to the end. // The nl variable controls whether we send a newline or not. // If true, and if the destination object is a Text object, we append // a newline. - sendText:(const char *)buffer andNewline:(BOOL) nl to:anObject { int len; if ( !anObject ) return nil; if ( [anObject respondsTo:@selector(setStringValue:)] ) { return [anObject setStringValue:buffer]; } // Otherwise it should be a text object. if ( ! [anObject isKindOfClassNamed:"Text"] ) { return nil; } len = [anObject textLength]; [anObject setSel:len:len]; [anObject replaceSel:buffer]; if ( nl ) { len = [anObject textLength]; [anObject setSel:len:len]; [anObject replaceSel:"\n"]; } [anObject scrollSelToVisible]; return self; } /* * just read a line that looks like * ALERT;Are you sure?;Yes;No * Do an NXRunAlertPanel and write back the result as an integer. */ - doAlert { MiscString *message, *b1, *b2, *b3; int r; char rbuf[10]; message = [currentLine extractPart:1 useAsDelimiter: ';']; b1 = [currentLine extractPart:2 useAsDelimiter: ';']; b2 = [currentLine extractPart:3 useAsDelimiter: ';']; b3 = [currentLine extractPart:4 useAsDelimiter: ';']; r = NXRunAlertPanel([NXApp appName], [message stringValue], [b1 stringValue], [b2 stringValue], [b3 stringValue] ); sprintf(rbuf, "%d", r); [process send:rbuf withNewline:YES]; [message free]; [b1 free]; [b2 free]; [b3 free]; return self; } - doOpen { id op = [OpenPanel new]; if ( [op runModalForDirectory:NULL file:NULL] ) { [process send: [op filename] withNewline:YES]; } else { [process send:"\n" withNewline:NO]; } return self; } /* * Do whatever is appropriate upon reading a complete line from the shell. */ - handleCompleteLine { /* * Is it one of our special strings? */ /* * Run special magic commands that write back answers when * we see certain strings in the output. */ MiscString *var, *value; id varObj; // ALERT;Are you sure?;Yes;No if ( [currentLine cmp:"ALERT" n:5] == 0 ) { return [self doAlert]; } else if ( [currentLine cmp:"OPEN" n:4] == 0 ) { return [self doOpen]; } if ( [currentLine matchesRegex:"^v[0123456789]=" ] ) { var = [currentLine extractPart:0 useAsDelimiter:'=']; value = [currentLine extractPart:1 useAsDelimiter:'=']; // ooh tricky runtime stuff - get a pointer to the // instance var whose name is in "var" if ( object_getInstanceVariable(self, [var stringValue], (void *)&varObj) ) { if ( [varObj respondsTo:@selector(setStringValue:)] ) { [varObj setStringValue:[value stringValue]]; } } [var free]; [value free]; return self; } /* * Add the line to our lines array. */ [lines addString: [currentLine stringValue]]; /* * Create a MiscStringArray out of it by splitting it into * fields, and add it to the linesBrokenIntoFields List. */ [self splitupCurrentLine]; /* * Send the target/action message */ if ( _target && action && [_target respondsTo:action] ) { [_target perform:action with:self]; } /* * Send the line - and a newline - to standard output. */ [self sendText:[currentLine stringValue] andNewline:YES to:standardOutput]; /* * If standard out is a browser, add a row to it. */ if ( [standardOutput isKindOfClassNamed:"NXBrowser"] ) { id m = [standardOutput matrixInColumn:0]; id newCell; [m renewRows: [lines count] cols:1]; newCell = [[m cellList] lastObject]; [newCell setStringValue:[currentLine stringValue]]; [newCell setLeaf:YES]; [newCell setLoaded:YES]; [m sizeToCells]; [standardOutput sizeToFit]; } else if ( [standardOutput isKindOfClassNamed:"DBTableView"] || [standardOutput isKindOfClassNamed:"NXTableView"]) { ; // Don't need to do anything here, all handled in finishOutput // for now. } else if ( [standardOutput isKindOfClassNamed:"Matrix"] ) { [standardOutput selectCellWithTag:[self intValue]]; } else if ( [standardOutput isKindOfClassNamed:"MiscShell"] ) { /* * Ask the other process to read our string value and * place it on its stdin. This is a way that you can * pipeline two shell objects. */ [standardOutput takeStdinFrom:self]; } return self; } - (int) fieldsInString:(MiscString *)str { if ( [self useCustomDelimiters] ) { // n delimiters -> n+1 fields return [customDelimiters numWords] + 1; } else if ( [self delimiter] ) /* * count delimiters. * one delimiter = two fields. */ return [str numOfChar:[self delimiter]] + 1; else /* * delimiter is 0 - count words delimited by whitespace */ return [str numWords]; } - splitupCurrentLine { int i; MiscStringArray *a = [[MiscStringArray alloc] init]; MiscString *curLine = [[lines strings] lastObject]; // Extract each field in turn, add it to the array for this line for ( i = 0; i < [self fieldsInString:curLine]; i++ ) { [a addString: [[self field:i ofString:curLine] stringValue]]; } [linesBrokenIntoFields addObject:a]; return self; } /* * This is here so that we can do clever things if our standardOutput * is a table view object. This method is automatically called when * the outlet is initialized. * We need to tell the table view that we are its data source, and * we need to set identifiers for each of its columns. Although those * identifiers are normally objects, it's apparently ok to use * other 32-bit quantities such as integers, so we'll * just set the identifier of column "i" to be "i". * * TODO - is it legal to be doing this here? is the table view set up * properly when setStandardOutput is called? */ - setStandardOutput:newOutput { int i; standardOutput = newOutput; if ([standardOutput isKindOfClassNamed:"DBTableView"] || [standardOutput isKindOfClassNamed:"NXTableView"] ) { [standardOutput setDataSource:self]; /* * Become its delegate so we get column moved messages */ [standardOutput setDelegate:self]; /* * Put numeric identifiers on each of its columns. */ for ( i = 0; i < [standardOutput columnCount]; i++ ) [[standardOutput columnAt:i] setIdentifier:(void *)i]; } return self; } /* * startOutput does any special initialization of certain kinds of * output objects. */ - startOutput { return self; } /* * finishOutput is called after the subprocess has executed, and can * be used to do any sort of output display cleanup. */ - finishOutput { if ([standardOutput isKindOfClassNamed:"DBTableView"] || [standardOutput isKindOfClassNamed:"NXTableView"]) { [standardOutput reloadData:self]; } else if ( [standardOutput isKindOfClassNamed:"NXBrowser"] ) { [standardOutput display]; } return self; } /* * Here is a shellsort function, from Kernighan & Ritchie, page 116, * modified to pass a 3rd bonus parameter to the comparison routine. /* * Resort the output lines based on the value of column n. * We do this by creating a ListSortedByFields which duplicates * the existing string list then we tell the ListSortedByFields to * sort itself. * * Multiplying by sortWhenColumnsMove does the right thing for * ascending vs descending sorts. */ - (int)fieldComp:(int)n forLines:(int)a :(int)b { id field1 = [self line:a field:n]; id field2 = [self line:b field:n]; int rval; // If field1 looks like a number, compare numerically. // TODO - do the right thing if it looks like // a time value (HH:MM) if ( [field1 matchesRegex:":[0-9][0-9]$" ] ) { // Replace the ":" with a ".", which will // make the comparison work just as if it was a float. [field1 replace: ":" with: "."]; [field2 replace: ":" with: "."]; } if ( [field1 matchesRegex:"^[0-9]"] ) { double n1, n2; n1 = atof([field1 stringValue]); n2 = atof( [field2 stringValue] ); // Check for a K or M suffix, multiply appropriately. // This is so I can sort the output of "ps" nicely. if ( [field1 endcmp:"M"] == 0 ) n1 *= 1024 * 1024; if ( [field1 endcmp:"K"] == 0 ) n1 *= 1024; if ( [field2 endcmp:"M"] == 0) n2 *= 1024 * 1024; if ( [field2 endcmp:"K"] == 0) n2 *= 1024; rval = (n1 < n2) ? -1 : (n1 > n2) ? 1 : 0; } else { /* * Do a regular string comparison. */ rval = [field1 compareTo: field2]; } return rval * [self sortWhenColumnsMove]; } - resort:(int)fieldNumber { List * strings = [lines strings]; id a, b; int c, d, stride; BOOL found; int n = [strings count]; /* * ShellSort, from the SortingInAction miniexample */ #define STRIDE_FACTOR 3 stride = 1; while ( stride <= n ) stride = stride * STRIDE_FACTOR + 1; while ( stride > (STRIDE_FACTOR - 1)) { stride = stride / STRIDE_FACTOR; for ( c = stride; c < n; c++ ) { found = NO; d = c - stride; while ( (d >= 0) && !found ) { if ( [self fieldComp:fieldNumber forLines:d:d+stride] > 0 ) { // Swap the "lines" array ... a = [strings objectAt:d]; b = [strings objectAt:d+stride]; [strings replaceObjectAt:d with:b]; [strings replaceObjectAt:d+stride with:a]; //and the linesBrokenIntoFields list a = [linesBrokenIntoFields objectAt:d]; b = [linesBrokenIntoFields objectAt:d+stride]; [linesBrokenIntoFields replaceObjectAt:d with:b]; [linesBrokenIntoFields replaceObjectAt:d+stride with:a]; d -= stride; } else found = YES; } } } return self; } /* * Utility routine to return a particular field of a particular string, * using our delimiter. If delimiter is 0, we look for blank-separated words; * otherwise we look for the particular delimited field. */ - field:(int)f ofString:s { id thisField = nil; int startPos, endPos; MiscString *delim; if ( [self useCustomDelimiters] ) { // customDelimiters is a string like this // 0-4 7-9 13-22 // that defines the boundaries of each field. So, in this // case, if we want field 2, we extract chars 13-22. delim = [[self customDelimiters] wordNum:f]; // delim is now something like "5-9", extract the // starting and ending positions. If it's "72-", that // means "72 to end of line" if ( [delim stringValue] && (sscanf( [delim stringValue], "%d-%d", &startPos, &endPos) == 2) ) { thisField = [s midFrom:startPos to:endPos]; } else if ( [delim stringValue ] && (sscanf( [delim stringValue], "%d-", &startPos) == 1) ) { thisField = [s midFrom:startPos to: [s length]]; } [delim free]; } else if ( [self delimiter] ) thisField = [s extractPart:f useAsDelimiter:[self delimiter]]; else { /* * take the column'th word. * Current bug in MiscString: wordNum:n for n > numWords returns * the last word rather than nil. So we check that. */ if ( f < [s numWords] ) thisField = [s wordNum:f]; else thisField = nil; } return thisField; } @end @implementation MiscShell(TableViewDelegate) - (unsigned int) rowCount { return [self lineCount]; } - (unsigned int) columnCount { return 1; // ??? todo - what do I put here? does it matter? } /* * This is a table view asking for the value at row aPosition, * column identifier. */ - getValueFor:identifier at:(unsigned int)aPosition into:aValue { [aValue setStringValue: [[self line:aPosition field:(int)identifier] stringValue] ]; return self; } /* * If you're the delegate of a DBTableView, you get these messages * when the columns are resized. Not sure just how I want to deal * with this yet. * * Current Plan - check the sortWhenColumnsMove variable. * If 0, do nothing. * If 1, resort ascending * If -1, resort descending */ - tableView:sender movedColumnFrom:(unsigned int) old to:(unsigned int) new { /* * Resort based on the identifier of the new first column. */ if ( [self sortWhenColumnsMove] ) { [self resort: (int)[[sender columnAt:0] identifier]]; [sender reloadData:self]; } return self; } @end @implementation MiscShell + initialize { if (self == [MiscShell class]) { /* * **** Archiving: READ ME **** After bumping the _VERSION, it is * considered common practice to add a comment line indicating the new * version number, date, and modifier. Optionally, the reason for the * change. There is no need to modify the setVersion message. BJM * 5/24/94 */ // version 0: initial. (sah) // version 1: adds customDelimiters var. (sah, sep 13 1994) // version 2: adds linesBrokenIntoFields array (sah, sep 14 1994) // version 3: fixes a bug with archiving BOOL vars (sah, jan 4 1995) [[MiscShell class] setVersion:MISC_SHELL_VERSION]; } return self; } - init { self = [super init]; script = [[MiscString alloc] init]; fullOutput = [[MiscString alloc] init]; currentLine = [[MiscString alloc] init]; lines = [[MiscStringArray alloc] init]; [self setDelimiter:0]; // means "parse words" [self setRunToCompletion:NO]; // run asynchronously [self setSortWhenColumnsMove:NO]; linesBrokenIntoFields = [[List alloc] init]; return self; } /* * Initialize, and run a non-interactive command and wait for it to terminate. */ - initWithCommand:(const char *)cmd { [self init]; [self setRunToCompletion:YES]; [self setScriptFromCString:cmd]; [self executeScript:self]; return self; } - free { if ( script ) script = [script free]; if ( process ) { [process terminate:self]; process = [process free]; } if ( fullOutput ) fullOutput = [fullOutput free]; [currentLine free]; return [super free]; } // Archiving methods - read:(NXTypedStream *)stream { int version; Class myClass = [self class]; int int1, int2; // compensate for old read/write bugs [super read:stream]; version = NXTypedStreamClassVersion(stream, "MiscShell"); switch (version) { case MISC_SHELL_VERSION: /* * Version 3 correctly reads/writes BOOL vars as "c", not "i" */ standardOutput = NXReadObject(stream); standardInput = NXReadObject(stream); standardError = NXReadObject(stream); v1 = NXReadObject(stream); v2 = NXReadObject(stream); v3 = NXReadObject(stream); v4 = NXReadObject(stream); script = NXReadObject( stream ); _target = NXReadObject( stream ); NXReadTypes(stream, ":", &action); NXReadTypes(stream, "@", &process); NXReadTypes(stream, "@", &fullOutput); NXReadTypes(stream, "c", &executionInProgress); NXReadTypes(stream, "@", ¤tLine); NXReadTypes(stream, "cc", ¤tLineIsComplete, &runToCompletion); NXReadTypes(stream, "@", &lines); NXReadTypes(stream, "c", &delimiter); NXReadTypes(stream, "c", &sortWhenColumnsMove); NXReadTypes(stream, "@", &customDelimiters); NXReadTypes(stream, "@", &linesBrokenIntoFields); // new for v2 break; case 2: /* * Version 2 adds the linesBrokenIntoFields var */ standardOutput = NXReadObject(stream); standardInput = NXReadObject(stream); standardError = NXReadObject(stream); v1 = NXReadObject(stream); v2 = NXReadObject(stream); v3 = NXReadObject(stream); v4 = NXReadObject(stream); script = NXReadObject( stream ); _target = NXReadObject( stream ); NXReadTypes(stream, ":", &action); NXReadTypes(stream, "@", &process); NXReadTypes(stream, "@", &fullOutput); // Written as int in versions <= 2, but really a char NXReadTypes(stream, "i", &int1); executionInProgress = int1; NXReadTypes(stream, "@", ¤tLine); // Written as int in versions <= 2, but really a char NXReadTypes(stream, "ii", &int1, &int2); currentLineIsComplete = int1; runToCompletion = int2; NXReadTypes(stream, "@", &lines); NXReadTypes(stream, "c", &delimiter); // Written as int in versions <= 2, but really a char NXReadTypes(stream, "i", &int1); sortWhenColumnsMove = int1; NXReadTypes(stream, "@", &customDelimiters); NXReadTypes(stream, "@", &linesBrokenIntoFields); // new for v2 break; case 1: /* * Version 1 adds a customDelimiters instance var */ standardOutput = NXReadObject(stream); standardInput = NXReadObject(stream); standardError = NXReadObject(stream); v1 = NXReadObject(stream); v2 = NXReadObject(stream); v3 = NXReadObject(stream); v4 = NXReadObject(stream); script = NXReadObject( stream ); _target = NXReadObject( stream ); NXReadTypes(stream, ":", &action); NXReadTypes(stream, "@", &process); NXReadTypes(stream, "@", &fullOutput); // Written as int in versions <= 2, but really a char NXReadTypes(stream, "i", &int1); executionInProgress = int1; NXReadTypes(stream, "@", ¤tLine); // Written as int in versions <= 2, but really a char NXReadTypes(stream, "ii", &int1, &int2); currentLineIsComplete = int1; runToCompletion = int2; NXReadTypes(stream, "@", &lines); NXReadTypes(stream, "c", &delimiter); // Written as int in versions <= 2, but really a char NXReadTypes(stream, "i", &int1); sortWhenColumnsMove = int1; NXReadTypes(stream, "@", &customDelimiters); linesBrokenIntoFields = [[List alloc] init]; // new - need one break; case 0: standardOutput = NXReadObject(stream); standardInput = NXReadObject(stream); standardError = NXReadObject(stream); v1 = NXReadObject(stream); v2 = NXReadObject(stream); v3 = NXReadObject(stream); v4 = NXReadObject(stream); script = NXReadObject( stream ); _target = NXReadObject( stream ); NXReadTypes(stream, ":", &action); NXReadTypes(stream, "@", &process); NXReadTypes(stream, "@", &fullOutput); // Written as int in versions <= 2, but really a char NXReadTypes(stream, "i", &int1); executionInProgress = int1; NXReadTypes(stream, "@", ¤tLine); // Written as int in versions <= 2, but really a char NXReadTypes(stream, "ii", &int1, &int2); currentLineIsComplete = int1; runToCompletion = int2; NXReadTypes(stream, "@", &lines); NXReadTypes(stream, "c", &delimiter); // Written as int in versions <= 2, but really a char NXReadTypes(stream, "i", &int1); sortWhenColumnsMove = int1; linesBrokenIntoFields = [[List alloc] init]; // new - need one break; default: NXLogError("[%s %s] - unknown version of %s in typed stream", [myClass name], sel_getName(_cmd), [myClass name]); break; } return self; } - write:(NXTypedStream *)stream { Class myClass = [self class]; [super write:stream]; switch (MISC_SHELL_VERSION) { case 3: NXWriteObjectReference( stream, standardOutput ); NXWriteObjectReference( stream, standardError ); NXWriteObjectReference( stream, standardInput ); NXWriteObjectReference( stream, v1 ); NXWriteObjectReference( stream, v2 ); NXWriteObjectReference( stream, v3 ); NXWriteObjectReference( stream, v4 ); NXWriteObject( stream, script ); NXWriteObjectReference( stream, _target ); NXWriteTypes(stream, ":", &action); NXWriteTypes(stream, "@", &process); NXWriteTypes(stream, "@", &fullOutput); NXWriteTypes(stream, "c", &executionInProgress); // BOOL NXWriteTypes(stream, "@", ¤tLine); NXWriteTypes(stream, "cc", ¤tLineIsComplete, &runToCompletion); // BOOL NXWriteTypes(stream, "@", &lines); NXWriteTypes(stream, "c", &delimiter); NXWriteTypes(stream, "c", &sortWhenColumnsMove); NXWriteTypes(stream, "@", &customDelimiters); // added in v1 NXWriteTypes(stream, "@", &linesBrokenIntoFields); // added in v2 break; default: NXLogError("[%s %s] - unknown version of %s in typed stream", [myClass name], sel_getName(_cmd), [myClass name]); break; } return self; } - target { return _target; } - setTarget:aTarget { _target = aTarget; return self; } - (SEL)action { return action; } - setAction:(SEL)anAction { action = anAction; return self; } /* * Our "string value" is the "current" line we've received * (which doesn't contain a newline.) */ - (const char *)stringValue { return [currentLine stringValue]; } /* * Int, double, float values are just derived from our string value. * note: no error checking as to whether the string value really is * a number. */ - (double) doubleValue { return ( atof([self stringValue]) ); } - (float) floatValue { return ( (float) atof([self stringValue]) ); } - (int) intValue { return ( atoi([self stringValue]) ); } /* * Set and retrieve the actual script. * We store it internally as a MiscString */ - (MiscString *)script { return script; } // Inspector sends this when the script changes - setScript:(MiscString *)newScript { return [self setScriptFromCString: [newScript stringValue]]; } - (MiscString *)customDelimiters { return customDelimiters; } // Inspector sends this when the script changes - setCustomDelimiters:(MiscString *)d { [customDelimiters free]; customDelimiters = [d copy]; return self; } // Messages from Controls - executeScript:sender { if ( executionInProgress ) { NXBeep(); return nil; } if ( process ) { [process terminate:self]; [process free]; } /* * Get rid of old accumulated output. */ [fullOutput setStringValue:""]; [currentLine setStringValue:""]; currentLineIsComplete = NO; [[lines strings] freeObjects]; [linesBrokenIntoFields freeObjects]; /* * Here we have special pre-output checks for certain kinds of * output objects. */ [self startOutput]; // Create a subprocess to execute the script. Don't run it just yet. process = [[MiscSubprocess alloc] init:NULL withDelegate:self]; // Set up subprocess environment here - a bunch of // environment variables that tell the process about // the values of v1, v2, etc. [self setupEnvironment:process forSender:sender]; // And finally start the process going. [process execute:[script stringValue] withPtys:NO asynchronously: [self runToCompletion]]; return self; } /* * These messages might arrive from either a control or a matrix of * controls. */ - executeFromStringValue:sender { if ( [sender isKindOfClassNamed:"Matrix"] ) sender = [sender selectedCell]; [self setScriptFromCString:[sender stringValue]]; return [self executeScript:sender]; } - executeFromTitle:sender { if ( [sender isKindOfClassNamed:"Matrix"] ) sender = [sender selectedCell]; [self setScriptFromCString:[sender title]]; return [self executeScript:sender]; } - executeFromAltTitle:sender { if ( [sender isKindOfClassNamed:"Matrix"] ) sender = [sender selectedCell]; [self setScriptFromCString:[sender altTitle]]; return [self executeScript:sender]; } - pause:sender { return [process pause:sender]; } - resume:sender { return [process resume:sender]; } - terminate:sender { return [process terminate:sender]; } /* * Methods that return particular lines, or fields within lines. */ - (int)lineCount { return [lines count]; } - (int) fieldsInLine:(int)n { return [[linesBrokenIntoFields objectAt:n] count]; } - (MiscStringArray *)lines { return lines; } /* * Return a copy of the MiscString that holds line number n. * We return a copy for consistency with the line:field: method, so that* * the caller is responsible for freeing both. * Just returning [[lines strings] objectAt:n] would hand back a string * owned by the MiscStringArray object, which the caller shouldn't free. * * Boy it will be nice when libFoundation_s.a is available everywhere * and we can do this more rationally. */ - (MiscString *)line:(int)n { id str = [[lines strings] objectAt:n]; if ( str ) return [str copy]; else return nil; } /* * Return the MiscString representing field f of line n; * the caller should NOT free it. */ - (MiscString *)line:(int)n field:(int)f { return [[[linesBrokenIntoFields objectAt:n] strings] objectAt:f]; } // MiscSubprocess delegate methods - subprocess:sender output:(const char *)buffer { /* * fullOutput records the entire output of the script, so add * the buffer to the end. */ [fullOutput cat:buffer]; /* * Now. We have to decide how this new chunk of data, with * possibly embedded newlines, affects the current line. We want * to fire off a target/action message every time we receive a newline. */ while ( *buffer ) { /* * If we have previously accumulated a complete newline, it's * no longer complete. */ if( currentLineIsComplete ) { [currentLine setStringValue:""]; currentLineIsComplete = NO; } /* * If we are looking at a newline, then the current output * line is complete, so fire the target/action message. */ if ( *buffer == '\n' ) { [self handleCompleteLine]; currentLineIsComplete = YES; } else { /* * Add this non-newline character */ [currentLine addChar:*buffer]; } buffer++; } return self; } // todo - if standardError and standardOutput are the same, why not // just forward this message to subprocess:stdoutOutput, which would merge // stderr and stdout handling (and allow target/action for stderr messages) - subprocess:sender stderrOutput:(const char *)buffer { if ( standardError ) [self sendText:buffer andNewline:NO to:standardError]; else fputs( buffer, stderr ); // To the console. } - subprocess:sender done:(int)status :(MiscSubprocessEndCode)code { executionInProgress = NO; [self finishOutput]; return self; } - (BOOL) runToCompletion { return runToCompletion; } - setRunToCompletion:(BOOL)c; { runToCompletion = c; return self; } - (int) sortWhenColumnsMove { return sortWhenColumnsMove; } - setSortWhenColumnsMove:(int)i { sortWhenColumnsMove = i; return self; } - (char)delimiter { return delimiter; } - setDelimiter:(char)c { delimiter = c; return self; } - setExecArgs:(const char *)a1:(const char *)a2:(const char *)a3 { ; // TODO - finish me return self; } /* * Methods for sending data to the standard input of a process. */ /* * takeStdinFrom:sender writes [sender stringValue] followed by a newline * to the process. */ - takeStdinFrom:sender { [self writeToStdin:[sender stringValue]]; [self writeToStdin:"\n"]; return self; } /* * writeToStdin writes a string verbatim to the standard input of the * process. */ - writeToStdin:(const char *)str { [process send:str withNewline:NO]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.