diff options
-rw-r--r-- | Source/SPAppController.m | 26 | ||||
-rw-r--r-- | Source/SPBundleCommandRunner.h | 42 | ||||
-rw-r--r-- | Source/SPBundleCommandRunner.m | 340 | ||||
-rw-r--r-- | Source/SPBundleEditorController.m | 6 | ||||
-rw-r--r-- | Source/SPBundleHTMLOutputController.m | 20 | ||||
-rw-r--r-- | Source/SPCopyTable.m | 19 | ||||
-rw-r--r-- | Source/SPStringAdditions.h | 22 | ||||
-rw-r--r-- | Source/SPStringAdditions.m | 485 | ||||
-rw-r--r-- | Source/SPTextView.m | 6 | ||||
-rw-r--r-- | Source/SPTextViewAdditions.m | 18 | ||||
-rw-r--r-- | UnitTests/SPStringAdditionsTest.h | 2 | ||||
-rw-r--r-- | UnitTests/SPStringAdditionsTest.m | 4 | ||||
-rw-r--r-- | sequel-pro.xcodeproj/project.pbxproj | 8 |
13 files changed, 553 insertions, 445 deletions
diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 4d3cc92b..2c03ddf4 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -40,6 +40,7 @@ #import "SPCustomQuery.h" #import "SPFavoritesController.h" #import "SPEditorTokens.h" +#import "SPBundleCommandRunner.h" #import <PSMTabBar/PSMTabBarControl.h> #import <Sparkle/Sparkle.h> @@ -572,7 +573,9 @@ YY_BUFFER_STATE yy_scan_string (const char *); NSError *error = nil; NSString *removePath = [[[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"] substringToIndex:([(NSString *)[[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"] length]-[SPBundleFileName length]-1)]; NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", removePath]; - [moveToTrashCommand runBashCommandWithEnvironment:nil atCurrentDirectoryPath:nil error:&error]; + + [SPBundleCommandRunner runBashCommand:moveToTrashCommand withEnvironment:nil atCurrentDirectoryPath:nil error:&error]; + if(error != nil) { alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while moving “%@” to Trash.", @"Open Files : Bundle : Already-Installed : Delete-Old-Error : Could not delete old bundle before installing new version."), removePath] defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : Already-Installed : Delete-Old-Error : OK button") @@ -981,15 +984,15 @@ YY_BUFFER_STATE yy_scan_string (const char *); return; } - NSString *output = [cmd runBashCommandWithEnvironment:env - atCurrentDirectoryPath:nil - callerInstance:self - contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - ([cmdData objectForKey:SPBundleFileNameKey])?:@"-", @"name", - NSLocalizedString(@"General", @"general menu item label"), @"scope", - uuid, SPBundleFileInternalexecutionUUID, - nil] - error:&err]; + NSString *output = [SPBundleCommandRunner runBashCommand:cmd + withEnvironment:env + atCurrentDirectoryPath:nil + callerInstance:self + contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: + ([cmdData objectForKey:SPBundleFileNameKey])?:@"-", @"name", + NSLocalizedString(@"General", @"general menu item label"), @"scope", + uuid, SPBundleFileInternalexecutionUUID, nil] + error:&err]; [[NSFileManager defaultManager] removeItemAtPath:bundleInputFilePath error:nil]; @@ -1681,7 +1684,8 @@ YY_BUFFER_STATE yy_scan_string (const char *); error = nil; NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", oldBundle]; - [moveToTrashCommand runBashCommandWithEnvironment:nil atCurrentDirectoryPath:nil error:&error]; + + [SPBundleCommandRunner runBashCommand:moveToTrashCommand withEnvironment:nil atCurrentDirectoryPath:nil error:&error]; if(error != nil) { NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while moving “%@” to Trash.", @"error while moving “%@” to trash"), [[installedBundleUUIDs objectForKey:[cmdDataOld objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"]] diff --git a/Source/SPBundleCommandRunner.h b/Source/SPBundleCommandRunner.h new file mode 100644 index 00000000..5c691c59 --- /dev/null +++ b/Source/SPBundleCommandRunner.h @@ -0,0 +1,42 @@ +// +// $Id$ +// +// SPBundleCommandRunner.h +// Sequel Pro +// +// Created by Stuart Connolly (stuconnolly.com) on May 6, 2012 +// Copyright (c) 2012 Stuart Connolly. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +@interface SPBundleCommandRunner : NSObject + +#ifndef SP_REFACTOR /* run self as bash commands */ + ++ (NSString *)runBashCommand:(NSString *)command withEnvironment:(NSDictionary *)shellEnvironment atCurrentDirectoryPath:(NSString *)path error:(NSError **)theError; ++ (NSString *)runBashCommand:(NSString *)command withEnvironment:(NSDictionary *)shellEnvironment atCurrentDirectoryPath:(NSString *)path callerInstance:(id)caller contextInfo:(NSDictionary *)contextInfo error:(NSError **)theError; + +#endif + +@end diff --git a/Source/SPBundleCommandRunner.m b/Source/SPBundleCommandRunner.m new file mode 100644 index 00000000..5d2336f2 --- /dev/null +++ b/Source/SPBundleCommandRunner.m @@ -0,0 +1,340 @@ +// +// $Id$ +// +// SPBundleCommandRunner.m +// Sequel Pro +// +// Created by Stuart Connolly (stuconnolly.com) on May 6, 2012 +// Copyright (c) 2012 Stuart Connolly. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPBundleCommandRunner.h" +#import "SPDatabaseDocument.h" + +// Defined to suppress warnings +@interface NSObject (SPBundleMethods) + +- (NSString *)lastBundleBlobFilesDirectory; +- (void)setLastBundleBlobFilesDirectory:(NSString *)path; + +@end + +// Defined to suppress warnings +@interface NSObject (SPWindowControllerTabMethods) + +- (id)selectedTableDocument; + +@end + +@implementation SPBundleCommandRunner + +#ifndef SP_REFACTOR /* run commands */ + +/** + * Run the supplied string as a BASH command(s) and return the result. + * This task can be interrupted by pressing ⌘. + * + * @param command The command to run + * @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 *)runBashCommand:(NSString *)command withEnvironment:(NSDictionary*)shellEnvironment atCurrentDirectoryPath:(NSString*)path error:(NSError**)theError +{ + return [SPBundleCommandRunner runBashCommand:command withEnvironment:shellEnvironment atCurrentDirectoryPath:path callerInstance:nil contextInfo:nil error:theError]; +} + +/** + * Run the supplied command as a BASH command(s) and return the result. + * This task can be interrupted by pressing ⌘. + * + * @param command The command to run + * @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 *)runBashCommand:(NSString *)command withEnvironment:(NSDictionary*)shellEnvironment atCurrentDirectoryPath:(NSString*)path callerInstance:(id)caller contextInfo:(NSDictionary*)contextInfo error:(NSError**)theError +{ + NSFileManager *fm = [NSFileManager defaultManager]; + + BOOL userTerminated = NO; + BOOL redirectForScript = NO; + BOOL isDir = NO; + + NSMutableArray *scriptHeaderArguments = [NSMutableArray array]; + NSString *scriptPath = @""; + NSString *uuid = (contextInfo && [contextInfo objectForKey:SPBundleFileInternalexecutionUUID]) ? [contextInfo objectForKey:SPBundleFileInternalexecutionUUID] : [NSString stringWithNewUUID]; + NSString *stdoutFilePath = [NSString stringWithFormat:@"%@_%@", SPBundleTaskOutputFilePath, uuid]; + NSString *scriptFilePath = [NSString stringWithFormat:@"%@_%@", SPBundleTaskScriptCommandFilePath, uuid]; + + [fm removeItemAtPath:scriptFilePath error:nil]; + [fm removeItemAtPath:stdoutFilePath error:nil]; + if([[NSApp delegate] lastBundleBlobFilesDirectory] != nil) + [fm removeItemAtPath:[[NSApp delegate] lastBundleBlobFilesDirectory] error:nil]; + + if([shellEnvironment objectForKey:SPBundleShellVariableBlobFileDirectory]) + [[NSApp delegate] setLastBundleBlobFilesDirectory:[shellEnvironment objectForKey:SPBundleShellVariableBlobFileDirectory]]; + + // 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([command length] > 3 && [command hasPrefix:@"#!"] && [shellEnvironment objectForKey:SPBundleShellVariableBundlePath]) { + + NSRange firstLineRange = NSMakeRange(2, [command rangeOfString:@"\n"].location - 2); + + [scriptHeaderArguments setArray:[[command 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 = [command substringWithRange:NSMakeRange(NSMaxRange(firstLineRange), [command 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/sh"]; + NSError *writeError = nil; + [command 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]; + [theEnv setDictionary:shellEnvironment]; + + [theEnv setObject:[[NSBundle mainBundle] pathForResource:@"appicon" ofType:@"icns"] forKey:SPBundleShellVariableIconFile]; + [theEnv setObject:[NSString stringWithFormat:@"%@/Contents/Resources", [[NSBundle mainBundle] bundlePath]] forKey:SPBundleShellVariableAppResourcesDirectory]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionNone] forKey:SPBundleShellVariableExitNone]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionReplaceSection] forKey:SPBundleShellVariableExitReplaceSelection]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionReplaceContent] forKey:SPBundleShellVariableExitReplaceContent]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionInsertAsText] forKey:SPBundleShellVariableExitInsertAsText]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionInsertAsSnippet] forKey:SPBundleShellVariableExitInsertAsSnippet]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsHTML] forKey:SPBundleShellVariableExitShowAsHTML]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsTextTooltip] forKey:SPBundleShellVariableExitShowAsTextTooltip]; + [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsHTMLTooltip] forKey:SPBundleShellVariableExitShowAsHTMLTooltip]; + + // Create and set an unique process ID for each SPDatabaseDocument which has to passed + // for each sequelpro:// scheme command as user to be able to identify the url scheme command. + // Furthermore this id is used to communicate with the called command as file name. + id doc = nil; + if([[[NSApp mainWindow] delegate] respondsToSelector:@selector(selectedTableDocument)]) + doc = [[[NSApp mainWindow] delegate] selectedTableDocument]; + // Check if connected + if([doc getConnection] == nil) + doc = nil; + else { + for (NSWindow *aWindow in [NSApp orderedWindows]) { + if([[[[aWindow windowController] class] description] isEqualToString:@"SPWindowController"]) { + if([[[aWindow windowController] documents] count] && [[[[[[aWindow windowController] documents] objectAtIndex:0] class] description] isEqualToString:@"SPDatabaseDocument"]) { + // Check if connected + if([[[[aWindow windowController] documents] objectAtIndex:0] getConnection]) + doc = [[[aWindow windowController] documents] objectAtIndex:0]; + else + doc = nil; + } + } + if(doc) break; + } + } + + if(doc != nil) { + + [doc setProcessID:uuid]; + + [theEnv setObject:uuid forKey:SPBundleShellVariableProcessID]; + [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryInputPathHeader, uuid] forKey:SPBundleShellVariableQueryFile]; + [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, uuid] forKey:SPBundleShellVariableQueryResultFile]; + [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, uuid] forKey:SPBundleShellVariableQueryResultStatusFile]; + [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultMetaPathHeader, uuid] forKey:SPBundleShellVariableQueryResultMetaFile]; + + if([doc shellVariables]) + [theEnv addEntriesFromDictionary:[doc shellVariables]]; + + if([theEnv objectForKey:SPBundleShellVariableCurrentEditedColumnName] && [[theEnv objectForKey:SPBundleShellVariableDataTableSource] isEqualToString:@"content"]) + [theEnv setObject:[theEnv objectForKey:SPBundleShellVariableSelectedTable] forKey:SPBundleShellVariableCurrentEditedTable]; + + } + + if(theEnv != nil && [theEnv count]) + [bashTask setEnvironment:theEnv]; + + if(path != nil) + [bashTask setCurrentDirectoryPath:path]; + else if([shellEnvironment objectForKey:SPBundleShellVariableBundlePath] && [fm fileExistsAtPath:[shellEnvironment objectForKey:SPBundleShellVariableBundlePath] isDirectory:&isDir] && isDir) + [bashTask setCurrentDirectoryPath:[shellEnvironment objectForKey:SPBundleShellVariableBundlePath]]; + + // STDOUT will be redirected to SPBundleTaskOutputFilePath in order to avoid nasty pipe programming due to block size reading + if([shellEnvironment objectForKey:SPBundleShellVariableInputFilePath]) + [bashTask setArguments:[NSArray arrayWithObjects:@"-c", [NSString stringWithFormat:@"%@ > %@ < %@", [scriptHeaderArguments componentsJoinedByString:@" "], stdoutFilePath, [shellEnvironment objectForKey:SPBundleShellVariableInputFilePath]], nil]]; + else + [bashTask setArguments:[NSArray arrayWithObjects:@"-c", [NSString stringWithFormat:@"%@ > %@", [scriptHeaderArguments componentsJoinedByString:@" "], stdoutFilePath], nil]]; + + NSPipe *stderr_pipe = [NSPipe pipe]; + [bashTask setStandardError:stderr_pipe]; + NSFileHandle *stderr_file = [stderr_pipe fileHandleForReading]; + [bashTask launch]; + NSInteger pid = -1; + if(caller != nil && [caller respondsToSelector:@selector(registerActivity:)]) { + // register command + pid = [bashTask processIdentifier]; + NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:pid], @"pid", + (contextInfo)?:[NSDictionary dictionary], @"contextInfo", + @"bashcommand", @"type", + [[NSDate date] descriptionWithCalendarFormat:@"%H:%M:%S" timeZone:nil locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]], @"starttime", + nil]; + [caller registerActivity:dict]; + } + + // Listen to ⌘. to terminate + while(1) { + if(![bashTask isRunning] || [bashTask processIdentifier] == 0) break; + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + usleep(1000); + 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]; + + // unregister BASH command if it was registered + if(pid > 0) { + [caller removeRegisteredActivity:pid]; + } + + // Remove files + [fm removeItemAtPath:scriptFilePath error:nil]; + if([theEnv objectForKey:SPBundleShellVariableQueryFile]) + [fm removeItemAtPath:[theEnv objectForKey:SPBundleShellVariableQueryFile] error:nil]; + if([theEnv objectForKey:SPBundleShellVariableQueryResultFile]) + [fm removeItemAtPath:[theEnv objectForKey:SPBundleShellVariableQueryResultFile] error:nil]; + if([theEnv objectForKey:SPBundleShellVariableQueryResultStatusFile]) + [fm removeItemAtPath:[theEnv objectForKey:SPBundleShellVariableQueryResultStatusFile] error:nil]; + if([theEnv objectForKey:SPBundleShellVariableQueryResultMetaFile]) + [fm removeItemAtPath:[theEnv objectForKey:SPBundleShellVariableQueryResultMetaFile] error:nil]; + if([theEnv objectForKey:SPBundleShellVariableInputTableMetaData]) + [fm removeItemAtPath:[theEnv objectForKey:SPBundleShellVariableInputTableMetaData] error:nil]; + + // If return from bash re-activate Sequel Pro + [NSApp activateIgnoringOtherApps:YES]; + + NSInteger status = [bashTask terminationStatus]; + NSData *errdata = [stderr_file readDataToEndOfFile]; + + // Check STDERR + if([errdata length] && (status < SPBundleRedirectActionNone || status > SPBundleRedirectActionLastCode)) { + [fm removeItemAtPath:stdoutFilePath error:nil]; + + if(status == 9 || userTerminated) return @""; + if(theError != NULL) { + 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:NSPOSIXErrorDomain + code:status + userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + errMessage, + NSLocalizedDescriptionKey, + nil]] autorelease]; + } else { + NSBeep(); + } + return @""; + } + + // Read STDOUT saved to file + if([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 @""; + NSMutableString *errMessage = [[[NSMutableString alloc] initWithData:errdata encoding:NSUTF8StringEncoding] autorelease]; + [errMessage replaceOccurrencesOfString:SPBundleTaskScriptCommandFilePath withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [errMessage length])]; + *theError = [[[NSError alloc] initWithDomain:NSPOSIXErrorDomain + code:status + userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + errMessage, + NSLocalizedDescriptionKey, + nil]] autorelease]; + } else { + NSBeep(); + } + if(status > SPBundleRedirectActionNone && status <= SPBundleRedirectActionLastCode) + return stdoutContent; + else + return @""; + } + } else { + NSLog(@"Couldn't read return string from “%@” by using UTF-8 encoding.", command); + NSBeep(); + } + } + + if (bashTask) [bashTask release]; + [fm removeItemAtPath:stdoutFilePath error:nil]; + return @""; +} + +#endif + +@end diff --git a/Source/SPBundleEditorController.m b/Source/SPBundleEditorController.m index 8d4d1c65..b807a52d 100644 --- a/Source/SPBundleEditorController.m +++ b/Source/SPBundleEditorController.m @@ -23,8 +23,8 @@ // More info at <http://code.google.com/p/sequel-pro/> #import "SPBundleEditorController.h" -#import "SPArrayAdditions.h" #import "SPMenuAdditions.h" +#import "SPBundleCommandRunner.h" #define kBundleNameKey @"bundleName" #define kChildrenKey @"_children_" @@ -1021,7 +1021,9 @@ // Use a AppleScript script since NSWorkspace performFileOperation or NSFileManager moveItemAtPath // have problems probably due access rights. NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", thePath]; - [moveToTrashCommand runBashCommandWithEnvironment:nil atCurrentDirectoryPath:nil error:&error]; + + [SPBundleCommandRunner runBashCommand:moveToTrashCommand withEnvironment:nil atCurrentDirectoryPath:nil error:&error]; + if(error != nil) { NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while moving “%@” to Trash.", @"Bundle Editor : Trash-Bundle(s)-Error : error dialog title"), thePath] defaultButton:NSLocalizedString(@"OK", @"Bundle Editor : Trash-Bundle(s)-Error : OK button") diff --git a/Source/SPBundleHTMLOutputController.m b/Source/SPBundleHTMLOutputController.m index cff150c0..d31a5d28 100644 --- a/Source/SPBundleHTMLOutputController.m +++ b/Source/SPBundleHTMLOutputController.m @@ -26,6 +26,7 @@ #import "SPAlertSheets.h" #import "SPPrintAccessory.h" #import "SPAppController.h" +#import "SPBundleCommandRunner.h" @class WebScriptCallFrame; @@ -659,7 +660,7 @@ NSString *output = nil; if(uuid == nil) - output = [command runBashCommandWithEnvironment:nil atCurrentDirectoryPath:nil error:&err]; + output = [SPBundleCommandRunner runBashCommand:command withEnvironment:nil atCurrentDirectoryPath:nil error:&err]; else { NSMutableDictionary *theEnv = [NSMutableDictionary dictionary]; [theEnv addEntriesFromDictionary:[[NSApp delegate] shellEnvironmentForDocument:nil]]; @@ -668,15 +669,16 @@ [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, uuid] forKey:SPBundleShellVariableQueryResultFile]; [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, uuid] forKey:SPBundleShellVariableQueryResultStatusFile]; [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultMetaPathHeader, uuid] forKey:SPBundleShellVariableQueryResultMetaFile]; - output = [command runBashCommandWithEnvironment:theEnv + + output = [SPBundleCommandRunner runBashCommand:command + withEnvironment:theEnv atCurrentDirectoryPath:nil - callerInstance:[NSApp delegate] - contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - @"JavaScript", @"name", - NSLocalizedString(@"General", @"general menu item label"), @"scope", - uuid, SPBundleFileInternalexecutionUUID, - nil] - error:&err]; + callerInstance:[NSApp delegate] + contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: + @"JavaScript", @"name", + NSLocalizedString(@"General", @"general menu item label"), @"scope", + uuid, SPBundleFileInternalexecutionUUID, nil] + error:&err]; } if(err != nil) { diff --git a/Source/SPCopyTable.m b/Source/SPCopyTable.m index ea0fe720..edc29db9 100644 --- a/Source/SPCopyTable.m +++ b/Source/SPCopyTable.m @@ -40,6 +40,8 @@ #import "SPAppController.h" #endif #import "SPTablesList.h" +#import "SPBundleCommandRunner.h" + #import <SPMySQL/SPMySQL.h> NSInteger SPEditMenuCopy = 2001; @@ -1407,15 +1409,14 @@ static const NSInteger kBlobAsImageFile = 4; } - NSString *output = [cmd runBashCommandWithEnvironment:env - atCurrentDirectoryPath:nil - callerInstance:[[NSApp delegate] frontDocument] - contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - ([cmdData objectForKey:SPBundleFileNameKey])?:@"-", @"name", - NSLocalizedString(@"Data Table", @"data table menu item label"), @"scope", - uuid, SPBundleFileInternalexecutionUUID, - nil] - error:&err]; + NSString *output = [SPBundleCommandRunner runBashCommand:cmd withEnvironment:env + atCurrentDirectoryPath:nil + callerInstance:[[NSApp delegate] frontDocument] + contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: + ([cmdData objectForKey:SPBundleFileNameKey])?:@"-", @"name", + NSLocalizedString(@"Data Table", @"data table menu item label"), @"scope", + uuid, SPBundleFileInternalexecutionUUID, nil] + error:&err]; [[NSFileManager defaultManager] removeItemAtPath:bundleInputFilePath error:nil]; diff --git a/Source/SPStringAdditions.h b/Source/SPStringAdditions.h index a45fbe0b..83ab2470 100644 --- a/Source/SPStringAdditions.h +++ b/Source/SPStringAdditions.h @@ -23,12 +23,12 @@ // // More info at <http://code.google.com/p/sequel-pro/> -/* +/** * NSStringUTF8String(@"a String") function can be used to speed up * the convertion from a NSString to NSData or const char* resp. * NSData *d = [aStr UTF8String]; :== NSData *d = NSStringUTF8String(aStr); */ -static inline const char* NSStringUTF8String(NSString* self) +static inline const char *NSStringUTF8String(NSString *self) { typedef const char* (*SPUTF8StringMethodPtr)(NSString*, SEL); static SPUTF8StringMethodPtr SPNSStringGetUTF8String; @@ -37,7 +37,7 @@ static inline const char* NSStringUTF8String(NSString* self) return to_return; } -static inline void NSMutableAttributedStringAddAttributeValueRange (NSMutableAttributedString* self, NSString* aStr, id aValue, NSRange aRange) +static inline void NSMutableAttributedStringAddAttributeValueRange(NSMutableAttributedString *self, NSString *aStr, id aValue, NSRange aRange) { typedef void (*SPMutableAttributedStringAddAttributeValueRangeMethodPtr)(NSMutableAttributedString*, SEL, NSString*, id, NSRange); static SPMutableAttributedStringAddAttributeValueRangeMethodPtr SPMutableAttributedStringAddAttributeValueRange; @@ -46,7 +46,7 @@ static inline void NSMutableAttributedStringAddAttributeValueRange (NSMutableAtt return; } -static inline id NSMutableAttributedStringAttributeAtIndex (NSMutableAttributedString* self, NSString* aStr, NSUInteger anIndex, NSRangePointer aRange) +static inline id NSMutableAttributedStringAttributeAtIndex(NSMutableAttributedString *self, NSString *aStr, NSUInteger anIndex, NSRangePointer aRange) { typedef id (*SPMutableAttributedStringAttributeAtIndexMethodPtr)(NSMutableAttributedString*, SEL, NSString*, NSUInteger, NSRangePointer); static SPMutableAttributedStringAttributeAtIndexMethodPtr SPMutableAttributedStringAttributeAtIndex; @@ -59,8 +59,7 @@ static inline id NSMutableAttributedStringAttributeAtIndex (NSMutableAttributedS + (NSString *)stringForByteSize:(long long)byteSize; + (NSString *)stringForTimeInterval:(double)timeInterval; -+ (NSString*)stringWithNewUUID; - ++ (NSString *)stringWithNewUUID; - (NSString *)rot13; - (NSString *)HTMLEscapeString; @@ -70,16 +69,11 @@ static inline id NSMutableAttributedStringAttributeAtIndex (NSMutableAttributedS - (NSArray *)lineRangesForRange:(NSRange)aRange; - (NSString *)createViewSyntaxPrettifier; -- (NSString*)getGeomFromTextString; +- (NSString *)getGeomFromTextString; -- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)charSet options:(NSUInteger)mask; -- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)charSet; +- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet *)charSet; +- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet *)charSet options:(NSUInteger)mask; - (CGFloat)levenshteinDistanceWithWord:(NSString *)stringB; -#ifndef SP_REFACTOR /* run self as bash commands */ -- (NSString *)runBashCommandWithEnvironment:(NSDictionary*)shellEnvironment atCurrentDirectoryPath:(NSString*)path callerInstance:(id)caller contextInfo:(NSDictionary*)contextInfo error:(NSError**)theError; -- (NSString *)runBashCommandWithEnvironment:(NSDictionary*)shellEnvironment atCurrentDirectoryPath:(NSString*)path error:(NSError**)theError; -#endif - @end diff --git a/Source/SPStringAdditions.m b/Source/SPStringAdditions.m index 31dbbfc6..40a03e5c 100644 --- a/Source/SPStringAdditions.m +++ b/Source/SPStringAdditions.m @@ -25,26 +25,10 @@ #import "SPStringAdditions.h" #import "RegexKitLite.h" -#import "SPDatabaseDocument.h" - -// Defined to suppress warnings -@interface NSObject (SPBundleMethods) - -- (NSString *)lastBundleBlobFilesDirectory; -- (void)setLastBundleBlobFilesDirectory:(NSString *)path; - -@end - -// Defined to suppress warnings -@interface NSObject (SPWindowControllerTabMethods) - -- (id)selectedTableDocument; - -@end @interface NSString (PrivateAPI) -- (NSInteger)smallestOf:(NSInteger)a andOf:(NSInteger)b andOf:(NSInteger)c; +- (NSInteger)_smallestOf:(NSInteger)a andOf:(NSInteger)b andOf:(NSInteger)c; @end @@ -153,6 +137,7 @@ } timeInterval = (timeInterval / 3600); + [numberFormatter setFormat:@"#,##0 hours"]; return [numberFormatter stringFromNumber:[NSNumber numberWithDouble:timeInterval]]; @@ -161,45 +146,53 @@ /** * Returns a new created UUID string. */ -+ (NSString*)stringWithNewUUID ++ (NSString* )stringWithNewUUID { // Create a new UUID CFUUIDRef uuidObj = CFUUIDCreate(nil); // Get the string representation of the UUID - NSString *newUUID = (NSString*)CFUUIDCreateString(nil, uuidObj); + NSString *newUUID = (NSString *)CFUUIDCreateString(nil, uuidObj); + CFRelease(uuidObj); + return [newUUID autorelease]; } /** - * Returns ROT13 representation + * Returns the ROT13 representation of self. */ - (NSString *)rot13 { - NSMutableString *holder = [[NSMutableString alloc] init]; unichar theChar; - NSUInteger i; - - for(i = 0; i < [self length]; i++) { + NSMutableString *holder = [[NSMutableString alloc] init]; + + for (NSUInteger i = 0; i < [self length]; i++) + { theChar = [self characterAtIndex:i]; - if(theChar <= 122 && theChar >= 97) { - if(theChar + 13 > 122) + + if (theChar <= 122 && theChar >= 97) { + if (theChar + 13 > 122) { theChar -= 13; - else + } + else { theChar += 13; + } + [holder appendFormat:@"%C", (char)theChar]; - - } else if(theChar <= 90 && theChar >= 65) { - if((int)theChar + 13 > 90) + } + else if (theChar <= 90 && theChar >= 65) { + if ((int)theChar + 13 > 90) { theChar -= 13; - else + } + else { theChar += 13; + } [holder appendFormat:@"%C", theChar]; - - } else { + } + else { [holder appendFormat:@"%C", theChar]; } } @@ -234,9 +227,10 @@ } /** - * Returns the string quoted with backticks as required for MySQL identifiers - * eg.: tablename => `tablename` - * my`table => `my``table` + * Returns the string quoted with backticks as required for MySQL identifiers. + * + * eg.: tablename => `tablename` + * my`table => `my``table` */ - (NSString *)backtickQuotedString { @@ -244,9 +238,10 @@ } /** - * Returns the string quoted with ticks as required for MySQL identifiers - * eg.: tablename => 'tablename' - * my'table => 'my''table' + * Returns the string quoted with ticks as required for MySQL identifiers. + * + * eg.: tablename => 'tablename' + * my'table => 'my''table' */ - (NSString *)tickQuotedString { @@ -254,7 +249,7 @@ } /** - * + * Replaces an occurrences of underscores with a single space. */ - (NSString *)replaceUnderscoreWithSpace { @@ -262,7 +257,8 @@ } /** - * Returns a 'CREATE VIEW SYNTAX' string a bit more readable + * Returns a more readable version of a 'CREATE VIEW SYNTAX' string. + * * If the string doesn't match it returns the unchanged string. */ - (NSString *)createViewSyntaxPrettifier @@ -273,31 +269,30 @@ NSMutableString *tblSyntax = [NSMutableString stringWithCapacity:[self length]]; NSString * re = @"(.*?) AS select (.*?) (from.*)"; - // create view syntax + // Create view syntax matchedRange = [self rangeOfRegex:re options:(RKLMultiline|RKLDotAll) inRange:searchRange capture:1 error:&err]; - if(!matchedRange.length || matchedRange.length > [self length]) return([self description]); + if (!matchedRange.length || matchedRange.length > [self length]) return([self description]); [tblSyntax appendString:[self substringWithRange:matchedRange]]; [tblSyntax appendString:@"\nAS select\n "]; - // match all column definitions, split them by ',', and rejoin them by '\n' + // Match all column definitions, split them by ',', and rejoin them by '\n' matchedRange = [self rangeOfRegex:re options:(RKLMultiline|RKLDotAll) inRange:searchRange capture:2 error:&err]; - if(!matchedRange.length || matchedRange.length > [self length]) return([self description]); + if (!matchedRange.length || matchedRange.length > [self length]) return([self description]); - [tblSyntax appendString: - [[[self substringWithRange:matchedRange] componentsSeparatedByString:@"`,`"] componentsJoinedByString:@"`,\n `"]]; + [tblSyntax appendString:[[[self substringWithRange:matchedRange] componentsSeparatedByString:@"`,`"] componentsJoinedByString:@"`,\n `"]]; - // from ... at a new line + // From ... at a new line matchedRange = [self rangeOfRegex:re options:(RKLMultiline|RKLDotAll) inRange:searchRange capture:3 error:&err]; - if(!matchedRange.length || matchedRange.length > [self length]) return([self description]); + if (!matchedRange.length || matchedRange.length > [self length]) return([self description]); [tblSyntax appendString:@"\n"]; [tblSyntax appendString:[self substringWithRange:matchedRange]]; - // where clause at a new line if given + // Where clause at a new line if given [tblSyntax replaceOccurrencesOfString:@" where (" withString:@"\nwhere (" options:NSLiteralSearch range:NSMakeRange(0, [tblSyntax length])]; return(tblSyntax); @@ -312,20 +307,24 @@ */ - (NSArray *)lineRangesForRange:(NSRange)aRange { - NSMutableArray *lineRangesArray = [NSMutableArray array]; NSRange currentLineRange; + NSMutableArray *lineRangesArray = [NSMutableArray array]; // Check that the range supplied is valid - if not return an empty array. - if (aRange.location == NSNotFound || aRange.location + aRange.length > [self length]) + if (aRange.location == NSNotFound || aRange.location + aRange.length > [self length]) { return lineRangesArray; + } // Get the range of the first string covered by the specified range, and add it to the array currentLineRange = [self lineRangeForRange:NSMakeRange(aRange.location, 0)]; + [lineRangesArray addObject:NSStringFromRange(currentLineRange)]; // Loop through until the line end matches or surpasses the end of the specified range - while (currentLineRange.location + currentLineRange.length < aRange.location + aRange.length) { + while (currentLineRange.location + currentLineRange.length < aRange.location + aRange.length) + { currentLineRange = [self lineRangeForRange:NSMakeRange(currentLineRange.location + currentLineRange.length, 0)]; + [lineRangesArray addObject:NSStringFromRange(currentLineRange)]; } @@ -338,12 +337,11 @@ */ - (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet *)charSet options:(NSUInteger)mask { - NSRange range; - NSMutableString* newString = [NSMutableString string]; - NSUInteger len = [self length]; + NSUInteger len = [self length]; + NSMutableString *newString = [NSMutableString string]; mask &= ~NSBackwardsSearch; - range = NSMakeRange (0, len); + NSRange range = NSMakeRange (0, len); while (range.length) { @@ -351,12 +349,14 @@ NSUInteger pos = range.location; range = [self rangeOfCharacterFromSet:charSet options:mask range:range]; - if (range.location == NSNotFound) + + if (range.location == NSNotFound) { range = NSMakeRange (len, 0); + } - substringRange = NSMakeRange (pos, range.location - pos); - [newString appendString:[self - substringWithRange:substringRange]]; + substringRange = NSMakeRange(pos, range.location - pos); + + [newString appendString:[self substringWithRange:substringRange]]; range.location += range.length; range.length = len - range.location; @@ -374,16 +374,16 @@ } /** - * Calculate the distance between two string case-insensitively + * Calculate the distance between two string case-insensitively. */ - (CGFloat)levenshteinDistanceWithWord:(NSString *)stringB { - // normalize strings + // Normalize strings NSString * stringA = [NSString stringWithString: self]; - [stringA stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]]; - [stringB stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + [stringA stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + [stringB stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + stringA = [stringA lowercaseString]; stringB = [stringB lowercaseString]; @@ -392,32 +392,33 @@ NSInteger n = [stringA length]; NSInteger m = [stringB length]; - if( n++ != 0 && m++ != 0 ) { - - d = malloc( sizeof(NSInteger) * m * n ); + if (n++ != 0 && m++ != 0) + { + d = malloc(sizeof(NSInteger) * m * n); - for( k = 0; k < n; k++) + for (k = 0; k < n; k++) + { d[k] = k; + } - for( k = 0; k < m; k++) - d[ k * n ] = k; - - for( i = 1; i < n; i++ ) - for( j = 1; j < m; j++ ) { + for (k = 0; k < m; k++) { + d[ k * n ] = k; + } - if( [stringA characterAtIndex: i-1] == [stringB characterAtIndex: j-1] ) - cost = 0; - else - cost = 1; + for (i = 1; i < n; i++) + for (j = 1; j < m; j++) + { + cost = ([stringA characterAtIndex:i - 1] == [stringB characterAtIndex:j - 1]) ? 0 : 1; - d[ j * n + i ] = [self smallestOf: d [ (j - 1) * n + i ] + 1 - andOf: d[ j * n + i - 1 ] + 1 - andOf: d[ (j - 1) * n + i -1 ] + cost ]; + d[j * n + i] = + [self _smallestOf:d[(j - 1) * n + i] + 1 + andOf:d[j * n + i - 1] + 1 + andOf:d[(j - 1) * n + i -1] + cost]; } - distance = d[ n * m - 1 ]; + distance = d[n * m - 1]; - free( d ); + free(d); return distance; } @@ -430,322 +431,32 @@ */ - (NSString*)getGeomFromTextString { - NSString *geomStr = [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - if(![self rangeOfString:@")"].length || [self length] < 5) return @"NULL"; + if (![self rangeOfString:@")"].length || [self length] < 5) return @"NULL"; // No SRID - if([geomStr hasSuffix:@")"]) + if ([geomStr hasSuffix:@")"]) { return [NSString stringWithFormat:@"GeomFromText('%@')", geomStr]; - // Has SRID - else { - NSUInteger idx = [geomStr length]-1; - while(idx>1) { - if([geomStr characterAtIndex:idx] == ')') - break; - idx--; - } - return [NSString stringWithFormat:@"GeomFromText('%@'%@)", - [geomStr substringToIndex:idx+1], [geomStr substringFromIndex:idx+1]]; - } - -} - -#ifndef SP_REFACTOR /* run self as bash commands */ -/** - * 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 *)runBashCommandWithEnvironment:(NSDictionary*)shellEnvironment atCurrentDirectoryPath:(NSString*)path callerInstance:(id)caller contextInfo:(NSDictionary*)contextInfo error:(NSError**)theError -{ - - NSFileManager *fm = [NSFileManager defaultManager]; - - BOOL userTerminated = NO; - BOOL redirectForScript = NO; - BOOL isDir = NO; - - NSMutableArray *scriptHeaderArguments = [NSMutableArray array]; - NSString *scriptPath = @""; - NSString *uuid = (contextInfo && [contextInfo objectForKey:SPBundleFileInternalexecutionUUID]) ? [contextInfo objectForKey:SPBundleFileInternalexecutionUUID] : [NSString stringWithNewUUID]; - NSString *stdoutFilePath = [NSString stringWithFormat:@"%@_%@", SPBundleTaskOutputFilePath, uuid]; - NSString *scriptFilePath = [NSString stringWithFormat:@"%@_%@", SPBundleTaskScriptCommandFilePath, uuid]; - - [fm removeItemAtPath:scriptFilePath error:nil]; - [fm removeItemAtPath:stdoutFilePath error:nil]; - if([[NSApp delegate] lastBundleBlobFilesDirectory] != nil) - [fm removeItemAtPath:[[NSApp delegate] lastBundleBlobFilesDirectory] error:nil]; - - if([shellEnvironment objectForKey:SPBundleShellVariableBlobFileDirectory]) - [[NSApp delegate] setLastBundleBlobFilesDirectory:[shellEnvironment objectForKey:SPBundleShellVariableBlobFileDirectory]]; - - // 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:@"#!"] && [shellEnvironment objectForKey:SPBundleShellVariableBundlePath]) { - - 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/sh"]; - 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]; - [theEnv setDictionary:shellEnvironment]; - - [theEnv setObject:[[NSBundle mainBundle] pathForResource:@"appicon" ofType:@"icns"] forKey:SPBundleShellVariableIconFile]; - [theEnv setObject:[NSString stringWithFormat:@"%@/Contents/Resources", [[NSBundle mainBundle] bundlePath]] forKey:SPBundleShellVariableAppResourcesDirectory]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionNone] forKey:SPBundleShellVariableExitNone]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionReplaceSection] forKey:SPBundleShellVariableExitReplaceSelection]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionReplaceContent] forKey:SPBundleShellVariableExitReplaceContent]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionInsertAsText] forKey:SPBundleShellVariableExitInsertAsText]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionInsertAsSnippet] forKey:SPBundleShellVariableExitInsertAsSnippet]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsHTML] forKey:SPBundleShellVariableExitShowAsHTML]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsTextTooltip] forKey:SPBundleShellVariableExitShowAsTextTooltip]; - [theEnv setObject:[NSNumber numberWithInteger:SPBundleRedirectActionShowAsHTMLTooltip] forKey:SPBundleShellVariableExitShowAsHTMLTooltip]; - - // Create and set an unique process ID for each SPDatabaseDocument which has to passed - // for each sequelpro:// scheme command as user to be able to identify the url scheme command. - // Furthermore this id is used to communicate with the called command as file name. - id doc = nil; - if([[[NSApp mainWindow] delegate] respondsToSelector:@selector(selectedTableDocument)]) - doc = [[[NSApp mainWindow] delegate] selectedTableDocument]; - // Check if connected - if([doc getConnection] == nil) - doc = nil; else { - for (NSWindow *aWindow in [NSApp orderedWindows]) { - if([[[[aWindow windowController] class] description] isEqualToString:@"SPWindowController"]) { - if([[[aWindow windowController] documents] count] && [[[[[[aWindow windowController] documents] objectAtIndex:0] class] description] isEqualToString:@"SPDatabaseDocument"]) { - // Check if connected - if([[[[aWindow windowController] documents] objectAtIndex:0] getConnection]) - doc = [[[aWindow windowController] documents] objectAtIndex:0]; - else - doc = nil; - } - } - if(doc) break; - } - } - - if(doc != nil) { - - [doc setProcessID:uuid]; - - [theEnv setObject:uuid forKey:SPBundleShellVariableProcessID]; - [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryInputPathHeader, uuid] forKey:SPBundleShellVariableQueryFile]; - [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, uuid] forKey:SPBundleShellVariableQueryResultFile]; - [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, uuid] forKey:SPBundleShellVariableQueryResultStatusFile]; - [theEnv setObject:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultMetaPathHeader, uuid] forKey:SPBundleShellVariableQueryResultMetaFile]; - - if([doc shellVariables]) - [theEnv addEntriesFromDictionary:[doc shellVariables]]; - - if([theEnv objectForKey:SPBundleShellVariableCurrentEditedColumnName] && [[theEnv objectForKey:SPBundleShellVariableDataTableSource] isEqualToString:@"content"]) - [theEnv setObject:[theEnv objectForKey:SPBundleShellVariableSelectedTable] forKey:SPBundleShellVariableCurrentEditedTable]; - - } - - if(theEnv != nil && [theEnv count]) - [bashTask setEnvironment:theEnv]; - - if(path != nil) - [bashTask setCurrentDirectoryPath:path]; - else if([shellEnvironment objectForKey:SPBundleShellVariableBundlePath] && [fm fileExistsAtPath:[shellEnvironment objectForKey:SPBundleShellVariableBundlePath] isDirectory:&isDir] && isDir) - [bashTask setCurrentDirectoryPath:[shellEnvironment objectForKey:SPBundleShellVariableBundlePath]]; - - // STDOUT will be redirected to SPBundleTaskOutputFilePath in order to avoid nasty pipe programming due to block size reading - if([shellEnvironment objectForKey:SPBundleShellVariableInputFilePath]) - [bashTask setArguments:[NSArray arrayWithObjects:@"-c", [NSString stringWithFormat:@"%@ > %@ < %@", [scriptHeaderArguments componentsJoinedByString:@" "], stdoutFilePath, [shellEnvironment objectForKey:SPBundleShellVariableInputFilePath]], nil]]; - else - [bashTask setArguments:[NSArray arrayWithObjects:@"-c", [NSString stringWithFormat:@"%@ > %@", [scriptHeaderArguments componentsJoinedByString:@" "], stdoutFilePath], nil]]; - - NSPipe *stderr_pipe = [NSPipe pipe]; - [bashTask setStandardError:stderr_pipe]; - NSFileHandle *stderr_file = [stderr_pipe fileHandleForReading]; - [bashTask launch]; - NSInteger pid = -1; - if(caller != nil && [caller respondsToSelector:@selector(registerActivity:)]) { - // register command - pid = [bashTask processIdentifier]; - NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:pid], @"pid", - (contextInfo)?:[NSDictionary dictionary], @"contextInfo", - @"bashcommand", @"type", - [[NSDate date] descriptionWithCalendarFormat:@"%H:%M:%S" timeZone:nil locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]], @"starttime", - nil]; - [caller registerActivity:dict]; - } - - // Listen to ⌘. to terminate - while(1) { - if(![bashTask isRunning] || [bashTask processIdentifier] == 0) break; - NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - usleep(1000); - 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]; - - // unregister BASH command if it was registered - if(pid > 0) { - [caller removeRegisteredActivity:pid]; - } - - // Remove files - [fm removeItemAtPath:scriptFilePath error:nil]; - if([theEnv objectForKey:SPBundleShellVariableQueryFile]) - [fm removeItemAtPath:[theEnv objectForKey:SPBundleShellVariableQueryFile] error:nil]; - if([theEnv objectForKey:SPBundleShellVariableQueryResultFile]) - [fm removeItemAtPath:[theEnv objectForKey:SPBundleShellVariableQueryResultFile] error:nil]; - if([theEnv objectForKey:SPBundleShellVariableQueryResultStatusFile]) - [fm removeItemAtPath:[theEnv objectForKey:SPBundleShellVariableQueryResultStatusFile] error:nil]; - if([theEnv objectForKey:SPBundleShellVariableQueryResultMetaFile]) - [fm removeItemAtPath:[theEnv objectForKey:SPBundleShellVariableQueryResultMetaFile] error:nil]; - if([theEnv objectForKey:SPBundleShellVariableInputTableMetaData]) - [fm removeItemAtPath:[theEnv objectForKey:SPBundleShellVariableInputTableMetaData] error:nil]; - - // If return from bash re-activate Sequel Pro - [NSApp activateIgnoringOtherApps:YES]; - - NSInteger status = [bashTask terminationStatus]; - NSData *errdata = [stderr_file readDataToEndOfFile]; - - // Check STDERR - if([errdata length] && (status < SPBundleRedirectActionNone || status > SPBundleRedirectActionLastCode)) { - [fm removeItemAtPath:stdoutFilePath error:nil]; - - if(status == 9 || userTerminated) return @""; - if(theError != NULL) { - 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:NSPOSIXErrorDomain - code:status - userInfo:[NSDictionary dictionaryWithObjectsAndKeys: - errMessage, - NSLocalizedDescriptionKey, - nil]] autorelease]; - } else { - NSBeep(); - } - return @""; - } - - // Read STDOUT saved to file - if([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 @""; - NSMutableString *errMessage = [[[NSMutableString alloc] initWithData:errdata encoding:NSUTF8StringEncoding] autorelease]; - [errMessage replaceOccurrencesOfString:SPBundleTaskScriptCommandFilePath withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [errMessage length])]; - *theError = [[[NSError alloc] initWithDomain:NSPOSIXErrorDomain - code:status - userInfo:[NSDictionary dictionaryWithObjectsAndKeys: - errMessage, - NSLocalizedDescriptionKey, - nil]] autorelease]; - } else { - NSBeep(); - } - if(status > SPBundleRedirectActionNone && status <= SPBundleRedirectActionLastCode) - return stdoutContent; - else - return @""; - } - } else { - NSLog(@"Couldn't read return string from “%@” by using UTF-8 encoding.", self); - NSBeep(); + NSUInteger idx = [geomStr length] - 1; + + while (idx > 1) + { + if ([geomStr characterAtIndex:idx] == ')') break; + + idx--; } + + return [NSString stringWithFormat:@"GeomFromText('%@'%@)", [geomStr substringToIndex:idx + 1], [geomStr substringFromIndex:idx + 1]]; } - - 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 *)runBashCommandWithEnvironment:(NSDictionary*)shellEnvironment atCurrentDirectoryPath:(NSString*)path error:(NSError**)theError -{ - return [self runBashCommandWithEnvironment:shellEnvironment atCurrentDirectoryPath:path callerInstance:nil contextInfo:nil error:theError]; } -#endif /** * Returns the minimum of a, b and c. */ -- (NSInteger)smallestOf:(NSInteger)a andOf:(NSInteger)b andOf:(NSInteger)c +- (NSInteger)_smallestOf:(NSInteger)a andOf:(NSInteger)b andOf:(NSInteger)c { NSInteger min = a; diff --git a/Source/SPTextView.m b/Source/SPTextView.m index 5466bd83..ac9bf3a2 100644 --- a/Source/SPTextView.m +++ b/Source/SPTextView.m @@ -40,8 +40,10 @@ #ifndef SP_REFACTOR /* headers */ #import "SPAppController.h" #endif -#import <SPMySQL/SPMySQL.h> #import "SPDatabaseStructure.h" +#import "SPBundleCommandRunner.h" + +#import <SPMySQL/SPMySQL.h> #pragma mark - #pragma mark lex init @@ -1853,7 +1855,7 @@ NSInteger _alphabeticSort(id string1, id string2, void *reverse) NSRange cmdRange = [theHintString rangeOfRegex:@"(?s)(?<!\\\\)\\$\\(\\s*(.*)\\s*\\)" capture:1L]; if(cmdRange.length) { NSError *err = nil; - NSString *cmdResult = [[theHintString substringWithRange:cmdRange] runBashCommandWithEnvironment:nil atCurrentDirectoryPath:nil error:&err]; + NSString *cmdResult = [SPBundleCommandRunner runBashCommand:[theHintString substringWithRange:cmdRange] withEnvironment:nil atCurrentDirectoryPath:nil error:&err]; if(err == nil) { [theHintString replaceCharactersInRange:tagRange withString:cmdResult]; } else if([err code] != 9) { // Suppress an error message if command was killed diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m index 8c5043cf..e8eb838e 100644 --- a/Source/SPTextViewAdditions.m +++ b/Source/SPTextViewAdditions.m @@ -35,6 +35,7 @@ #import "SPTextView.h" #import "SPWindowController.h" #import "SPDatabaseDocument.h" +#import "SPBundleCommandRunner.h" @implementation NSTextView (SPTextViewAdditions) @@ -651,15 +652,14 @@ return; } - NSString *output = [cmd runBashCommandWithEnvironment:env - atCurrentDirectoryPath:nil - callerInstance:[(SPAppController*)[NSApp delegate] frontDocument] - contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - ([cmdData objectForKey:SPBundleFileNameKey])?:@"-", @"name", - NSLocalizedString(@"Input Field", @"input field menu item label"), @"scope", - uuid, SPBundleFileInternalexecutionUUID, - nil] - error:&err]; + NSString *output = [SPBundleCommandRunner runBashCommand:cmd withEnvironment:env + atCurrentDirectoryPath:nil + callerInstance:[(SPAppController*)[NSApp delegate] frontDocument] + contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: + ([cmdData objectForKey:SPBundleFileNameKey])?:@"-", @"name", + NSLocalizedString(@"Input Field", @"input field menu item label"), @"scope", + uuid, SPBundleFileInternalexecutionUUID, nil] + error:&err]; [[NSFileManager defaultManager] removeItemAtPath:bundleInputFilePath error:nil]; diff --git a/UnitTests/SPStringAdditionsTest.h b/UnitTests/SPStringAdditionsTest.h index 66570b35..786df76f 100644 --- a/UnitTests/SPStringAdditionsTest.h +++ b/UnitTests/SPStringAdditionsTest.h @@ -27,4 +27,6 @@ @interface SPStringAdditionsTest : SenTestCase +- (void)testStringByRemovingCharactersInSet; + @end diff --git a/UnitTests/SPStringAdditionsTest.m b/UnitTests/SPStringAdditionsTest.m index 7f151b3a..b0143a0f 100644 --- a/UnitTests/SPStringAdditionsTest.m +++ b/UnitTests/SPStringAdditionsTest.m @@ -34,7 +34,7 @@ static NSString *SPUTFTestString = @"In der Kürze liegt die Würz"; /** * stringByRemovingCharactersInSet test case. */ -/*- (void)testStringByRemovingCharactersInSet +- (void)testStringByRemovingCharactersInSet { NSString *charsToRemove = @"abc',ü"; @@ -55,6 +55,6 @@ static NSString *SPUTFTestString = @"In der Kürze liegt die Würz"; expectedUTFString, @"The following characters should have been removed %@", charsToRemove); -}*/ +} @end diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 21ae7fae..79ab2e70 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 1713C740140D8AEF00CFD461 /* SPQueryDocumentsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1713C73F140D8AEF00CFD461 /* SPQueryDocumentsController.m */; }; 1713C75F140D8D5900CFD461 /* SPQueryConsoleDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1713C75E140D8D5900CFD461 /* SPQueryConsoleDataSource.m */; }; 17148565125F5FF500321285 /* SPDatabaseCharacterSets.m in Sources */ = {isa = PBXBuildFile; fileRef = 17148564125F5FF500321285 /* SPDatabaseCharacterSets.m */; }; + 1717F9661557E0450065C036 /* SPStringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1789343B0F30C1DD0097539A /* SPStringAdditions.m */; }; 17292443107AC41000B21980 /* SPXMLExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 17292442107AC41000B21980 /* SPXMLExporter.m */; }; 172A65110F7BED7A001E861A /* SPConsoleMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 172A65100F7BED7A001E861A /* SPConsoleMessage.m */; }; 173284EA1088FEDE0062E892 /* SPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 173284E91088FEDE0062E892 /* SPConstants.m */; }; @@ -65,6 +66,7 @@ 176059B913361D390098E162 /* SPTableCopyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 112730551180788A000737FD /* SPTableCopyTest.m */; }; 176059BA13361D3A0098E162 /* SPDatabaseCopyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1198F5C31174EF3F00670590 /* SPDatabaseCopyTest.m */; }; 1761FD480EF03A6F00331368 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1761FD460EF03A6F00331368 /* MainMenu.xib */; }; + 176E14D115570FE300FAF326 /* SPBundleCommandRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 176E14D015570FE300FAF326 /* SPBundleCommandRunner.m */; }; 177E792E0FCB54EC00E9E122 /* database-small.png in Resources */ = {isa = PBXBuildFile; fileRef = 177E792B0FCB54EC00E9E122 /* database-small.png */; }; 177E792F0FCB54EC00E9E122 /* dummy-small.png in Resources */ = {isa = PBXBuildFile; fileRef = 177E792C0FCB54EC00E9E122 /* dummy-small.png */; }; 177E79300FCB54EC00E9E122 /* table-small-square.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 177E792D0FCB54EC00E9E122 /* table-small-square.tiff */; }; @@ -667,6 +669,8 @@ 1760599E1336199D0098E162 /* SPMenuAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMenuAdditionsTests.m; sourceTree = "<group>"; }; 1761FD470EF03A6F00331368 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = "<group>"; }; 1761FD9D0EF0488900331368 /* build-version.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = "build-version.pl"; sourceTree = "<group>"; }; + 176E14CF15570FE300FAF326 /* SPBundleCommandRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPBundleCommandRunner.h; sourceTree = "<group>"; }; + 176E14D015570FE300FAF326 /* SPBundleCommandRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPBundleCommandRunner.m; sourceTree = "<group>"; }; 177E792B0FCB54EC00E9E122 /* database-small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "database-small.png"; sourceTree = "<group>"; }; 177E792C0FCB54EC00E9E122 /* dummy-small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "dummy-small.png"; sourceTree = "<group>"; }; 177E792D0FCB54EC00E9E122 /* table-small-square.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "table-small-square.tiff"; sourceTree = "<group>"; }; @@ -1463,6 +1467,8 @@ children = ( BC68BFC5128D4EAE004907D9 /* SPBundleEditorController.h */, BC68BFC6128D4EAE004907D9 /* SPBundleEditorController.m */, + 176E14CF15570FE300FAF326 /* SPBundleCommandRunner.h */, + 176E14D015570FE300FAF326 /* SPBundleCommandRunner.m */, BC77C5E2129AA69E009AD832 /* SPBundleHTMLOutputController.h */, BC77C5E3129AA69E009AD832 /* SPBundleHTMLOutputController.m */, ); @@ -3074,6 +3080,7 @@ 1798F1C4155018E2004B0AB8 /* SPMutableArrayAdditionsTest.m in Sources */, 17DB5F441555CA300046834B /* SPMutableArrayAdditions.m in Sources */, 17DB5F4A1555CA810046834B /* SPMenuAdditions.m in Sources */, + 1717F9661557E0450065C036 /* SPStringAdditions.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3297,6 +3304,7 @@ 1798F19B1550185B004B0AB8 /* SPTreeNode.m in Sources */, 1798F19E15501892004B0AB8 /* SPFlippedView.m in Sources */, 17D5B49E1553059F00EF3BB3 /* SPViewCopy.m in Sources */, + 176E14D115570FE300FAF326 /* SPBundleCommandRunner.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; |