Skip to content

Commit

Permalink
[Issue #85] add shell operation
Browse files Browse the repository at this point in the history
  • Loading branch information
Jigish Patel committed Oct 17, 2012
1 parent e3ebb14 commit 9c0906b
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 3 deletions.
18 changes: 18 additions & 0 deletions Slate.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
3BA1BA5C162DD7190026774E /* TestMathUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA5B162DD7190026774E /* TestMathUtils.m */; };
3BA1BA62162DDFEF0026774E /* TestExpressionPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA61162DDFEF0026774E /* TestExpressionPoint.m */; };
3BA1BA65162E01A30026774E /* TestStringTokenizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA64162E01A20026774E /* TestStringTokenizer.m */; };
3BA1BA68162F3E530026774E /* ShellOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA67162F3E530026774E /* ShellOperation.m */; };
3BA1BA6B162F4CFF0026774E /* ShellUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA6A162F4CFF0026774E /* ShellUtils.m */; };
3BA1BA6E162F51760026774E /* TestShellUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA6D162F51760026774E /* TestShellUtils.m */; };
3BA7485913B1D0F500CFA792 /* MathUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA7485813B1D0F500CFA792 /* MathUtils.m */; };
3BA85C9A16276DAB00FAAF0B /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BA85C9916276DAB00FAAF0B /* Sparkle.framework */; };
3BA85C9B16276E9E00FAAF0B /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3BA85C9916276DAB00FAAF0B /* Sparkle.framework */; };
Expand Down Expand Up @@ -159,6 +162,12 @@
3BA1BA61162DDFEF0026774E /* TestExpressionPoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestExpressionPoint.m; sourceTree = "<group>"; };
3BA1BA63162E01A20026774E /* TestStringTokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestStringTokenizer.h; sourceTree = "<group>"; };
3BA1BA64162E01A20026774E /* TestStringTokenizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestStringTokenizer.m; sourceTree = "<group>"; };
3BA1BA66162F3E530026774E /* ShellOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShellOperation.h; sourceTree = "<group>"; };
3BA1BA67162F3E530026774E /* ShellOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShellOperation.m; sourceTree = "<group>"; };
3BA1BA69162F4CFE0026774E /* ShellUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShellUtils.h; sourceTree = "<group>"; };
3BA1BA6A162F4CFF0026774E /* ShellUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShellUtils.m; sourceTree = "<group>"; };
3BA1BA6C162F51760026774E /* TestShellUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestShellUtils.h; sourceTree = "<group>"; };
3BA1BA6D162F51760026774E /* TestShellUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestShellUtils.m; sourceTree = "<group>"; };
3BA7485713B1D0F500CFA792 /* MathUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MathUtils.h; sourceTree = "<group>"; };
3BA7485813B1D0F500CFA792 /* MathUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MathUtils.m; sourceTree = "<group>"; };
3BA85C9916276DAB00FAAF0B /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = "<group>"; };
Expand Down Expand Up @@ -282,6 +291,8 @@
3BA1BA61162DDFEF0026774E /* TestExpressionPoint.m */,
3BA1BA5A162DD7190026774E /* TestMathUtils.h */,
3BA1BA5B162DD7190026774E /* TestMathUtils.m */,
3BA1BA6C162F51760026774E /* TestShellUtils.h */,
3BA1BA6D162F51760026774E /* TestShellUtils.m */,
3BA1BA63162E01A20026774E /* TestStringTokenizer.h */,
3BA1BA64162E01A20026774E /* TestStringTokenizer.m */,
);
Expand Down Expand Up @@ -380,6 +391,8 @@
3B7EB3A6138F3F6800EBEC2B /* ResizeOperation.m */,
3BE702B1161F9D5000260716 /* SequenceOperation.h */,
3BE702B2161F9D5000260716 /* SequenceOperation.m */,
3BA1BA66162F3E530026774E /* ShellOperation.h */,
3BA1BA67162F3E530026774E /* ShellOperation.m */,
3BDDA17814FD95E200829D2A /* SnapshotOperation.h */,
3BDDA17914FD95E200829D2A /* SnapshotOperation.m */,
3B9B301F151C830E0069D95E /* SwitchAppQuittingOverlayView.h */,
Expand Down Expand Up @@ -417,6 +430,8 @@
3BB5380313AEDA190005CFFC /* ScreenState.m */,
3B66A56013ADBDFD0015EDD5 /* ScreenWrapper.h */,
3B66A56113ADBDFD0015EDD5 /* ScreenWrapper.m */,
3BA1BA69162F4CFE0026774E /* ShellUtils.h */,
3BA1BA6A162F4CFF0026774E /* ShellUtils.m */,
3B58154215054EBD0078D568 /* SlateLogger.h */,
3B7EB4CA138F72D900EBEC2B /* StringTokenizer.h */,
3B7EB4CB138F72D900EBEC2B /* StringTokenizer.m */,
Expand Down Expand Up @@ -685,6 +700,8 @@
3BE702B3161F9D5000260716 /* SequenceOperation.m in Sources */,
3BF57C9E16216C2200C0F89B /* VisibilityOperation.m in Sources */,
3B883A0D1627725200FF3D8C /* RelaunchOperation.m in Sources */,
3BA1BA68162F3E530026774E /* ShellOperation.m in Sources */,
3BA1BA6B162F4CFF0026774E /* ShellUtils.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -698,6 +715,7 @@
3BA1BA5C162DD7190026774E /* TestMathUtils.m in Sources */,
3BA1BA62162DDFEF0026774E /* TestExpressionPoint.m in Sources */,
3BA1BA65162E01A30026774E /* TestStringTokenizer.m in Sources */,
3BA1BA6E162F51760026774E /* TestShellUtils.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
3 changes: 3 additions & 0 deletions Slate/Constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ extern NSString *const HIDE;
extern NSString *const SHOW;
extern NSString *const TOGGLE;
extern NSString *const RELAUNCH;
extern NSString *const SHELL;

// Parameters and Options
extern NSString *const CENTER;
Expand Down Expand Up @@ -227,6 +228,8 @@ extern NSString *const KEYBOARD_LAYOUT_DVORAK;
extern NSString *const PADDING;
extern NSString *const CURRENT;
extern NSString *const ALL_BUT;
extern NSString *const WAIT;
extern NSString *const PATH;

// Directions and Anchors
extern NSString *const UP;
Expand Down
3 changes: 3 additions & 0 deletions Slate/Constants.m
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@
NSString *const SHOW = @"show";
NSString *const TOGGLE = @"toggle";
NSString *const RELAUNCH = @"relaunch";
NSString *const SHELL = @"shell";

// Parameters and Options
NSString *const CENTER = @"center";
Expand Down Expand Up @@ -226,6 +227,8 @@
NSString *const PADDING = @"padding";
NSString *const CURRENT = @"current";
NSString *const ALL_BUT = @"all-but:";
NSString *const WAIT = @"wait";
NSString *const PATH = @"path:";

// Directions and Anchors
NSString *const UP = @"up";
Expand Down
3 changes: 3 additions & 0 deletions Slate/Operation.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#import "SequenceOperation.h"
#import "VisibilityOperation.h"
#import "RelaunchOperation.h"
#import "ShellOperation.h"

@implementation Operation

Expand Down Expand Up @@ -101,6 +102,8 @@ + (id)operationFromString:(NSString *)opString {
operation = [VisibilityOperation visibilityOperationFromString:opString];
} else if ([op isEqualToString:RELAUNCH]) {
operation = [RelaunchOperation relaunchOperationFromString:opString];
} else if ([op isEqualToString:SHELL]) {
operation = [ShellOperation shellOperationFromString:opString];
} else {
SlateLogger(@"ERROR: Unrecognized operation '%@'", opString);
@throw([NSException exceptionWithName:@"Unrecognized Operation" reason:[NSString stringWithFormat:@"Unrecognized operation '%@' in '%@'", op, opString] userInfo:nil]);
Expand Down
39 changes: 39 additions & 0 deletions Slate/ShellOperation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// ShellOperation.h
// Slate
//
// Created by Jigish Patel on 10/17/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// 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 3 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.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses

#import "Operation.h"

@interface ShellOperation : Operation {
NSString *command;
NSArray *args;
NSString *currentPath;
BOOL waitForExit;
}

@property NSString *command;
@property NSArray *args;
@property NSString *currentPath;
@property BOOL waitForExit;

- (id)initWithCommand:(NSString *)theCommand args:(NSArray *)theArgs waitForExit:(BOOL)theWaitForExit currentPath:(NSString *)theCurrentPath;

+ (id)shellOperationFromString:(NSString *)shellOperation;

@end
99 changes: 99 additions & 0 deletions Slate/ShellOperation.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// ShellOperation.m
// Slate
//
// Created by Jigish Patel on 10/17/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// 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 3 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.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses

#import "ShellOperation.h"
#import "Constants.h"
#import "SlateLogger.h"
#import "StringTokenizer.h"
#import "ShellUtils.h"

@implementation ShellOperation

@synthesize command, args, waitForExit, currentPath;

- (id)initWithCommand:(NSString *)theCommand args:(NSArray *)theArgs waitForExit:(BOOL)theWaitForExit currentPath:(NSString *)theCurrentPath {
self = [super init];
if (self) {
[self setCommand:theCommand];
[self setArgs:theArgs];
[self setWaitForExit:theWaitForExit];
[self setCurrentPath:theCurrentPath];
}
return self;
}

- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Shell Operation -----------------");
// We don't use the passed in AccessibilityWrapper or ScreenWrapper so they are nil. No need to waste time creating them here.
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:nil];
SlateLogger(@"----------------- End Shell Operation -----------------");
return success;
}

- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)iamalsonil {
NSTask *task = [ShellUtils run:[self command] args:[self args] wait:[self waitForExit] path:[self currentPath]];
return task != nil;
}

- (BOOL)testOperation {
return [ShellUtils commandExists:[self command]];
}

+ (id)shellOperationFromString:(NSString *)shellOperation {
// shell [wait] 'command'
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:shellOperation into:tokens quoteChars:[NSCharacterSet characterSetWithCharactersInString:QUOTES]];

if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", shellOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Shell operations require the following format: shell [wait] 'command'", shellOperation] userInfo:nil]);
}

BOOL waitForExit = NO;
NSString *currentPath = nil;
for (NSInteger i = 1; i < [tokens count] - 1; i++) {
if ([[tokens objectAtIndex:i] isEqualToString:WAIT]) {
waitForExit = YES;
} else if ([[tokens objectAtIndex:i] hasPrefix:PATH]) {
currentPath = [[tokens objectAtIndex:i] stringByReplacingOccurrencesOfString:PATH withString:EMPTY];
if ([currentPath hasPrefix:TILDA]) {
currentPath = [currentPath stringByExpandingTildeInPath];
}
}
}
NSString *commandAndArgs = [tokens lastObject];
NSMutableArray *commandAndArgsTokens = [NSMutableArray array];
[StringTokenizer tokenize:commandAndArgs into:commandAndArgsTokens];
if ([commandAndArgsTokens count] < 1) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", shellOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Shell operations require the following format: shell [wait] 'command'", shellOperation] userInfo:nil]);
}
NSString *command = [commandAndArgsTokens objectAtIndex:0];
NSMutableArray *args = [NSMutableArray array];
for (NSInteger i = 1; i < [commandAndArgsTokens count]; i++) {
[args addObject:[commandAndArgsTokens objectAtIndex:i]];
}

Operation *op = [[ShellOperation alloc] initWithCommand:command args:args waitForExit:waitForExit currentPath:currentPath];
return op;

}

@end
28 changes: 28 additions & 0 deletions Slate/ShellUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// ShellUtils.h
// Slate
//
// Created by Jigish Patel on 10/17/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// 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 3 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.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses

#import <Foundation/Foundation.h>

@interface ShellUtils : NSObject

+ (BOOL)commandExists:(NSString *)command;
+ (NSTask *)run:(NSString *)command args:(NSArray *)args wait:(BOOL)wait path:(NSString *)path;

@end
81 changes: 81 additions & 0 deletions Slate/ShellUtils.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// ShellUtils.m
// Slate
//
// Created by Jigish Patel on 10/17/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// 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 3 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.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses

#import "ShellUtils.h"
#import "Constants.h"

@implementation ShellUtils

+ (BOOL)commandExists:(NSString *)command {
if (command == nil || [EMPTY isEqualToString:command]) return NO;
@try {
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/command"];

NSArray *arguments;
arguments = [NSArray arrayWithObjects:@"-v", command, nil];
[task setArguments:arguments];

NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task setStandardInput:[NSPipe pipe]];

NSFileHandle *file;
file = [pipe fileHandleForReading];

[task launch];

NSData *data;
data = [file readDataToEndOfFile];

NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
if ([string isEqualToString:EMPTY]) {
return NO;
}
return YES;
} @catch (id ex) {
return NO;
}
}

+ (NSTask *)run:(NSString *)command args:(NSArray *)args wait:(BOOL)wait path:(NSString *)path {
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:command];
[task setArguments:args];
if (path != nil) [task setCurrentDirectoryPath:path];

NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task setStandardInput:[NSPipe pipe]];

NSFileHandle *file;
file = [pipe fileHandleForReading];

[task launch];
if (wait) [task waitUntilExit];
return task;
}

@end
1 change: 1 addition & 0 deletions Slate/StringTokenizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
+ (BOOL)isSpaceChar:(unichar)c;
+ (BOOL)isQuoteChar:(unichar)c quoteChars:(NSCharacterSet *)quoteChars;
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array;
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array quoteChars:(NSCharacterSet *)quotes;
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array maxTokens:(NSInteger)maxTokens;
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array maxTokens:(NSInteger)maxTokens quoteChars:(NSCharacterSet *)quotes;
+ (void)firstToken:(NSString *)s into:(NSMutableString *)token;
Expand Down
Loading

2 comments on commit 9c0906b

@richo
Copy link
Contributor

@richo richo commented on 9c0906b Nov 18, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm misreading this completely, your implementation of commandExists actually runs the command?

Surely you should just be searching PATH for an executable file with that name, or testing for the file's existance if it's name contains a slash?

@richo
Copy link
Contributor

@richo richo commented on 9c0906b Nov 20, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit, I misread this. You're shelling out to call command, but this is probably still not a great idea. The original comment doesn't make sense though. Disregard :)

Please sign in to comment.