Rev 6064 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
/** R.app : a Cocoa front end to: "R A Computer Language for Statistical Data Analysis"** R.app Copyright notes:* Copyright (C) 2004-12 The R Foundation* written by Stefano M. Iacus and Simon Urbanek*** R Copyright notes:* Copyright (C) 1995-1996 Robert Gentleman and Ross Ihaka* Copyright (C) 1998-2001 The R Development Core Team* Copyright (C) 2002-2004 The R Foundation** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation; either version 2 of the License, or* (at your option) any later version.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.** A copy of the GNU General Public License is available via WWW at* http://www.gnu.org/copyleft/gpl.html. You can also obtain it by* writing to the Free Software Foundation, Inc., 59 Temple Place,* Suite 330, Boston, MA 02111-1307 USA.** NSString_RAdditions.m** Created by Hans-J. Bibiko on 02/01/2012.**/#import "RGUI.h"#import "NSString_RAdditions.h"#import "RegexKitLite.h"#import "PreferenceKeys.h"@implementation NSString (NSString_RAdditions)/*** Returns a new created UUID string.*/+ (NSString*)stringWithNewUUID{// Create a new UUIDCFUUIDRef uuidObj = CFUUIDCreate(nil);// Get the string representation of the UUIDNSString *newUUID = (NSString*)CFUUIDCreateString(nil, uuidObj);CFRelease(uuidObj);return [newUUID autorelease];}/*** Run self as BASH command(s) and return the result.* This task can be interrupted by pressing ⌘.** @param shellEnvironment A dictionary of environment variable values whose keys are the variable names.** @param path The current directory for the bash command. If path is nil, the current directory is inherited from the process that created the receiver (normally /).** @param caller The SPDatabaseDocument which invoked that command to register the command for cancelling; if nil the command won't be registered.** @param name The menu title of the command.** @param theError If not nil and the bash command failed it contains the returned error message as NSLocalizedDescriptionKey**/- (NSString *)evaluateAsBashCommandWithEnvironment:(NSDictionary*)shellEnvironment atPath:(NSString*)path callerInstance:(id)caller contextInfo:(NSDictionary*)contextInfo error:(NSError**)theError ignoreOutput:(BOOL)ignoreOutput{NSFileManager *fm = [NSFileManager defaultManager];BOOL userTerminated = NO;BOOL redirectForScript = NO;BOOL isDir = NO;BOOL nonWaitingMode = ([self hasSuffix:@"&"]) ? YES : NO;NSMutableArray *scriptHeaderArguments = [NSMutableArray array];NSString *scriptPath = @"";NSString *uuid = (contextInfo && [contextInfo objectForKey:kBASHFileInternalexecutionUUID]) ? [contextInfo objectForKey:kBASHFileInternalexecutionUUID] : [NSString stringWithNewUUID];NSString *stdoutFilePath = [NSString stringWithFormat:@"%@_%@", kBASHTaskOutputFilePath, uuid];NSString *scriptFilePath = [NSString stringWithFormat:@"%@_%@", kBASHTaskScriptCommandFilePath, uuid];[fm removeItemAtPath:scriptFilePath error:nil];[fm removeItemAtPath:stdoutFilePath error:nil];// Parse first line for magic header #! ; if found save the script content and run the command after #! with that file.// This allows to write perl, ruby, osascript scripts natively.if([self length] > 3 && [self hasPrefix:@"#!"]) {NSRange firstLineRange = NSMakeRange(2, [self rangeOfString:@"\n"].location - 2);[scriptHeaderArguments setArray:[[self substringWithRange:firstLineRange] componentsSeparatedByString:@" "]];while([scriptHeaderArguments containsObject:@""])[scriptHeaderArguments removeObject:@""];if([scriptHeaderArguments count])scriptPath = [scriptHeaderArguments objectAtIndex:0];if([scriptPath hasPrefix:@"/"] && [fm fileExistsAtPath:scriptPath isDirectory:&isDir] && !isDir) {NSString *script = [self substringWithRange:NSMakeRange(NSMaxRange(firstLineRange), [self length] - NSMaxRange(firstLineRange))];NSError *writeError = nil;[script writeToFile:scriptFilePath atomically:YES encoding:NSUTF8StringEncoding error:&writeError];if(writeError == nil) {redirectForScript = YES;[scriptHeaderArguments addObject:scriptFilePath];} else {NSBeep();NSLog(@"Couldn't write script file.");}}} else {[scriptHeaderArguments addObject:@"/bin/bash"];NSError *writeError = nil;[self writeToFile:scriptFilePath atomically:YES encoding:NSUTF8StringEncoding error:&writeError];if(writeError == nil) {redirectForScript = YES;[scriptHeaderArguments addObject:scriptFilePath];} else {NSBeep();NSLog(@"Couldn't write script file.");}}NSTask *bashTask = [[NSTask alloc] init];[bashTask setLaunchPath:@"/bin/bash"];NSMutableDictionary *theEnv = [NSMutableDictionary dictionary];// set current environment variables to shell[theEnv setDictionary:[[NSProcessInfo processInfo] environment]];// overwrite or set additional variablesif(shellEnvironment) [theEnv addEntriesFromDictionary:shellEnvironment];// set exit codes[theEnv setObject:[NSNumber numberWithInteger:kBASHTaskRedirectActionNone] forKey:kBASHTaskShellVariableExitNone];[theEnv setObject:[NSNumber numberWithInteger:kBASHTaskRedirectActionReplaceSection] forKey:kBASHTaskShellVariableExitReplaceSelection];[theEnv setObject:[NSNumber numberWithInteger:kBASHTaskRedirectActionReplaceContent] forKey:kBASHTaskShellVariableExitReplaceContent];[theEnv setObject:[NSNumber numberWithInteger:kBASHTaskRedirectActionInsertAsText] forKey:kBASHTaskShellVariableExitInsertAsText];[theEnv setObject:[NSNumber numberWithInteger:kBASHTaskRedirectActionInsertAsSnippet] forKey:kBASHTaskShellVariableExitInsertAsSnippet];[theEnv setObject:[NSNumber numberWithInteger:kBASHTaskRedirectActionShowAsHTML] forKey:kBASHTaskShellVariableExitShowAsHTML];[theEnv setObject:[NSNumber numberWithInteger:kBASHTaskRedirectActionShowAsTextTooltip] forKey:kBASHTaskShellVariableExitShowAsTextTooltip];[theEnv setObject:[NSNumber numberWithInteger:kBASHTaskRedirectActionShowAsHTMLTooltip] forKey:kBASHTaskShellVariableExitShowAsHTMLTooltip];if(theEnv != nil && [theEnv count])[bashTask setEnvironment:theEnv];if(path != nil)[bashTask setCurrentDirectoryPath:path];// STDOUT will be redirected to kBASHTaskOutputFilePath in order to avoid nasty pipe programming due to block size readingif([shellEnvironment objectForKey:kBASHTaskShellVariableInputFilePath])[bashTask setArguments:[NSArray arrayWithObjects:@"-c", [NSString stringWithFormat:@"%@ > %@ < %@", [scriptHeaderArguments componentsJoinedByString:@" "], stdoutFilePath, [shellEnvironment objectForKey:kBASHTaskShellVariableInputFilePath]], nil]];else[bashTask setArguments:[NSArray arrayWithObjects:@"-c", [NSString stringWithFormat:@"%@ > %@", [scriptHeaderArguments componentsJoinedByString:@" "], stdoutFilePath], nil]];NSFileHandle *stderr_file = nil;NSPipe *stderr_pipe = nil;if(!nonWaitingMode && theError != NULL) {stderr_pipe = [NSPipe pipe];[bashTask setStandardError:stderr_pipe];stderr_file = [stderr_pipe fileHandleForReading];} else {[bashTask setStandardError:[NSFileHandle fileHandleForWritingAtPath:@"/dev/null"]];}[bashTask launch];NSInteger pid = -1;pid = [bashTask processIdentifier];if(!nonWaitingMode) {// Listen to ⌘. to terminatewhile(1) {if(![bashTask isRunning] || [bashTask processIdentifier] == 0) break;usleep(1000);NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMaskuntilDate:[NSDate distantPast]inMode:NSDefaultRunLoopModedequeue:YES];if(!event) continue;if ([event type] == NSKeyDown) {unichar key = [[event characters] length] == 1 ? [[event characters] characterAtIndex:0] : 0;if (([event modifierFlags] & NSCommandKeyMask) && key == '.') {[bashTask terminate];userTerminated = YES;break;}[NSApp sendEvent:event];} else {[NSApp sendEvent:event];}}[bashTask waitUntilExit];} else {if (bashTask) [bashTask release];[fm removeItemAtPath:scriptFilePath error:nil];[fm removeItemAtPath:stdoutFilePath error:nil];return @"";}// Remove files[fm removeItemAtPath:scriptFilePath error:nil];// If return from bash re-activate R.app[NSApp activateIgnoringOtherApps:YES];NSInteger status = [bashTask terminationStatus];NSData *errdata = nil;if(stderr_file) errdata = [stderr_file readDataToEndOfFile];if(status == 9 || userTerminated) {if(theError != NULL)*theError = [[[NSError alloc] initWithDomain:NSPOSIXErrorDomaincode:statususerInfo:[NSDictionary dictionaryWithObjectsAndKeys:NLS(@"User Termination"),NSLocalizedDescriptionKey,@"",@"terminated",nil]] autorelease];return @"";}// Check STDERRif(theError != NULL && errdata && [errdata length] && (status < kBASHTaskRedirectActionNone || status > kBASHTaskRedirectActionLastCode)) {[fm removeItemAtPath:stdoutFilePath error:nil];if(theError != NULL && errdata && [errdata length]) {NSMutableString *errMessage = [[[NSMutableString alloc] initWithData:errdata encoding:NSUTF8StringEncoding] autorelease];[errMessage replaceOccurrencesOfString:[NSString stringWithFormat:@"%@: ", scriptFilePath] withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [errMessage length])];*theError = [[[NSError alloc] initWithDomain:NSPOSIXErrorDomaincode:statususerInfo:[NSDictionary dictionaryWithObjectsAndKeys:errMessage,NSLocalizedDescriptionKey,nil]] autorelease];}return @"";}if(ignoreOutput && theError == NULL) {if (bashTask) [bashTask release];[fm removeItemAtPath:stdoutFilePath error:nil];return @"";}// Read STDOUT saved to fileif([fm fileExistsAtPath:stdoutFilePath isDirectory:nil]) {NSString *stdoutContent = [NSString stringWithContentsOfFile:stdoutFilePath encoding:NSUTF8StringEncoding error:nil];if(bashTask) [bashTask release], bashTask = nil;[fm removeItemAtPath:stdoutFilePath error:nil];if(stdoutContent != nil) {if (status == 0) {return stdoutContent;} else {if(theError != NULL) {if(status == 9 || userTerminated) return @"";if(stderr_file) {[stderr_file readDataToEndOfFile];NSMutableString *errMessage = [[[NSMutableString alloc] initWithData:errdata encoding:NSUTF8StringEncoding] autorelease];[errMessage replaceOccurrencesOfString:kBASHTaskScriptCommandFilePath withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [errMessage length])];*theError = [[[NSError alloc] initWithDomain:NSPOSIXErrorDomaincode:statususerInfo:[NSDictionary dictionaryWithObjectsAndKeys:errMessage,NSLocalizedDescriptionKey,nil]] autorelease];}}if(status > kBASHTaskRedirectActionNone && status <= kBASHTaskRedirectActionLastCode)return stdoutContent;elsereturn @"";}} else {NSLog(@"Couldn't read return string from “%@” by using UTF-8 encoding.", self);NSBeep();}}if (bashTask) [bashTask release];[fm removeItemAtPath:stdoutFilePath error:nil];return @"";}/*** Run self as BASH command(s) and return the result.* This task can be interrupted by pressing ⌘.** @param shellEnvironment A dictionary of environment variable values whose keys are the variable names.** @param path The current directory for the bash command. If path is nil, the current directory is inherited from the process that created the receiver (normally /).** @param theError If not nil and the bash command failed it contains the returned error message as NSLocalizedDescriptionKey**/- (NSString *)evaluateAsBashCommandWithEnvironment:(NSDictionary*)shellEnvironment atPath:(NSString*)path error:(NSError**)theError{return [self evaluateAsBashCommandWithEnvironment:shellEnvironment atPath:path callerInstance:nil contextInfo:nil error:theError ignoreOutput:NO];}/*** Run self as BASH command(s) and return the result.* This task can be interrupted by pressing ⌘.** @param theError If not nil and the bash command failed it contains the returned error message as NSLocalizedDescriptionKey**/- (NSString *)evaluateAsBashCommandAndError:(NSError**)theError{return [self evaluateAsBashCommandWithEnvironment:nil atPath:nil callerInstance:nil contextInfo:nil error:theError ignoreOutput:NO];}/*** Run self as BASH command(s) and return the result.* This task can be interrupted by pressing ⌘.**/- (NSString *)evaluateAsBashCommand{return [self evaluateAsBashCommandWithEnvironment:nil atPath:nil callerInstance:nil contextInfo:nil error:nil ignoreOutput:NO];}/*** Run self as BASH command(s).* This task can be interrupted by pressing ⌘.** @param shellEnvironment A dictionary of environment variable values whose keys are the variable names.** @param path The current directory for the bash command. If path is nil, the current directory is inherited from the process that created the receiver (normally /).** @param caller The SPDatabaseDocument which invoked that command to register the command for cancelling; if nil the command won't be registered.** @param name The menu title of the command.** @param theError If not nil and the bash command failed it contains the returned error message as NSLocalizedDescriptionKey**/- (void)runAsBashCommandWithEnvironment:(NSDictionary*)shellEnvironment atPath:(NSString*)path callerInstance:(id)caller contextInfo:(NSDictionary*)contextInfo error:(NSError**)theError{[self evaluateAsBashCommandWithEnvironment:shellEnvironment atPath:path callerInstance:caller contextInfo:contextInfo error:theError ignoreOutput:YES];}/*** Run self as BASH command(s).* This task can be interrupted by pressing ⌘.** @param shellEnvironment A dictionary of environment variable values whose keys are the variable names.** @param path The current directory for the bash command. If path is nil, the current directory is inherited from the process that created the receiver (normally /).** @param theError If not nil and the bash command failed it contains the returned error message as NSLocalizedDescriptionKey**/- (void)runAsBashCommandWithEnvironment:(NSDictionary*)shellEnvironment atPath:(NSString*)path error:(NSError**)theError{[self evaluateAsBashCommandWithEnvironment:shellEnvironment atPath:path callerInstance:nil contextInfo:nil error:theError ignoreOutput:YES];}/*** Run self as BASH command(s).* This task can be interrupted by pressing ⌘.** @param theError If not nil and the bash command failed it contains the returned error message as NSLocalizedDescriptionKey**/- (void)runAsBashCommandAndError:(NSError**)theError{[self evaluateAsBashCommandWithEnvironment:nil atPath:nil callerInstance:nil contextInfo:nil error:theError ignoreOutput:YES];}/*** Run self as BASH command(s).* This task can be interrupted by pressing ⌘.**/- (void)runAsBashCommand{[self evaluateAsBashCommandWithEnvironment:nil atPath:nil callerInstance:nil contextInfo:nil error:nil ignoreOutput:YES];}@end