diff --git a/README.md b/README.md index 41bb3f6..4535a19 100644 --- a/README.md +++ b/README.md @@ -6,55 +6,190 @@ A simple SSH shortcut menu for OS X ![How Shuttle works](https://raw.github.com/fitztrev/shuttle/gh-pages/img/how-shuttle-works.gif) -***Sidenote***: *Many people ask, so here's how I have [my terminal setup](https://github.com/fitztrev/shuttle/wiki/My-Terminal-Prompt).* +**Sidenote**: *Many people ask, so here's how I have [my terminal setup](https://github.com/fitztrev/shuttle/wiki/My-Terminal-Prompt).* ## Installation 1. Download [Shuttle](http://fitztrev.github.io/shuttle/) 2. Copy to Applications -## Customization +## JSON Path Change -The default, out-of-the-box configuration should be good enough to get started. However, if you're looking to customize the appearance further, here are a few advanced tips. +In your home directory create a file called ```~/.shuttle.path``` +In this file should be a single line with the path to the JSON settings file. -### Disabling `~/.ssh/config` hosts +``` +/Users/thshdw/Dropbox/shuttle/shuttle.json +``` +shuttle will read ```~/.shuttle.path``` first and use its contents as the path to your JSON file. + +## JSON Options +### Global settings +#### ```"editor": "VALUE",``` +_This changes the app that opens settings.json for editing (Global Setting)_ + +Possible values are ```default```, ```nano```, ```vi```, ```vim``` or any terminal based editor. +```default``` opens settings.json in whatever app is registered as the default for extension ```.json``` +``` +"editor": "vim", +``` +would open ```~/.shuttle.json``` in vim + +---- + +#### ```"launch_at_login": VALUE,``` +_This allows you to flag the shuttle.app to start automatically (Global Setting)_ + +Possible values are ```true``` or ```false``` + +---- + +#### ```"terminal": "VALUE",``` +_This allows you to set the default terminal (Global Setting)_ + +Possible values are ```Terminal.app``` or ```iTerm``` + +---- + +#### ```"iTerm_version": "VALUE",``` +_This changes the applescripts for iTerm (Global Setting)_ + +Possible values are ```stable``` or ```nightly``` + +**If ```terminal``` is set to ```iTerm``` this setting is mandatory** + +_This setting is ignored if your terminal is set to ```Terminal.app```_ + +---- + +#### ```"default_theme": "Homebrew",``` +_This sets the Terminal theme for all windows. (Global Setting)_ + +Possible values are the Profile names in your terminal preferences. iTerm ships with one Profile named "Default". OS X Terminal ships with several. To see the names see the preferences area of the terminal you are using. + +In iTerm the profile names are case sensitive. + +**Please ensure the theme names you set are valid. If shuttle passes theme "Dagobah" and it does not exist in iTerm or OS X Terminal then your command won't run. This is because the applescripts are not making any checks to see if the theme you passed actually exists within the terminal application.** + +This setting can be overwritten by the command level ```"theme"``` settings + +---- + +#### ```"open_in": "VALUE",``` +_This changes the default action for how commands are opened (Global Setting)_ + +Possible values are ```tab``` or ```new```. + +```tab``` opens the command in the active terminal in a new tab. + +```new``` opens the command in a new window. + +This setting can be overwritten by the command level ```"inTerminal"``` settings + +---- + +#### ```"show_ssh_config_hosts": VALUE,``` +_This changes parsing ssh config. By default, Shuttle will parse your ```~/.ssh/config``` file for hosts. (Global Setting)_ + +Possible values are ```false``` or ```true``` + +---- -By default, Shuttle will parse your `~/.ssh/config` file for hosts. +#### ```"ssh_config_ignore_hosts": ["VALUE", "VALUE"],``` +_This will ignore hosts in the ssh config. (Global Setting)_ -##### To disable all ~/.ssh/config entries: +Possible values are the hosts in your config that you want to ignore. If you had github.com and git.example.com in your ssh config, to ignore them you set: + +```"ssh_config_ignore_hosts": ["github.com", "git.example.com"],``` + +---- + +#### ```"ssh_config_ignore_keywords": ["VALUE"],``` +_This will ignore keywords in your ssh config. (Global Setting)_ + +Possible values are the keywords in your ssh config that you want to ignore. + +---- + +**Additional ssh config customization** +#### Nested menus for `~/.ssh/config` hosts + +##### Create a menu item at "work" > "servers" > "web01" ``` -"show_ssh_config_hosts": false, +Host work/servers/web01 + HostName user@web01.example.com ``` - -#### Disable specific hosts: +\- *or* - ``` -"ssh_config_ignore_hosts": ["github.com", "git.example.com"], +Host gandalf + # shuttle.name = work/servers/web01 (webserver) + HostName user@web01.example.com ``` -#### Disable hosts that contain a keyword: +### Command level settings +_Command level settings are unique to your command and will overwrite the Global setting equivalent_ + +#### ```"cmd": "VALUE"``` +_This is the command / script that will be launched in the terminal. (Command setting)_ +Where Value is a command or script. ``` -"ssh_config_ignore_keywords": ["git"], +"cmd": "ps aux | grep [s]sh" ``` +Would check for ssh processes. -### Nested menus for `~/.ssh/config` hosts +---- -#### Create a menu item at "work" > "servers" > "web01" +#### ```"name": "VALUE"``` +_This sets the text that will appear in shuttles drop down menu. (Command setting)_ +Were Value is the text you want to see in the drop down menu for this command. ``` -Host work/servers/web01 - HostName user@web01.example.com +"name": "SSH to my wordpress blog" ``` -\- *or* - + +This value can also set the title of the terminal window if ```"title" :"VALUE"``` is not set. + +---- + +#### ```"inTerminal": "VALUE",``` +_This sets how command will open in the terminal window. (Command setting)_ + +Possible values are ```new```, ```tab```, or ```current``` + +```new``` opens the command in a new terminal window. + +```tab``` opens the command in the active terminal window in a new tab. + +```current``` opens the command in the active terminal's window. + +When using using ```current``` I recommend that you wrap the command in some user input like this: ``` -Host gandalf - # shuttle.name = work/servers/web01 (webserver) - HostName user@web01.example.com +echo "are you sure y/n"; read sure; if [ "$sure" == "y" ]; then echo "running command" && ps aux | grep [s]sh; else echo "exiting..."; fi ``` +Do this as a precaution as it could be possible to run a command on the wrong host. + +---- + +#### ```"theme": "VALUE",``` +_This sets the theme for the terminal window. (Command setting)_ + +Possible values are the profile names for iTerm or OS X Terminal. + +If ```"theme"``` is not set and ```"default_theme"``` is not set then shuttle passes Profile ```Default``` for iTerm and Profile ```basic``` for OS X terminal. + +---- + +#### ```"title": "VALUE"``` +_This sets the text that will appear in the terminal's title bar. (Command setting)_ + +Where VALUE is the text you want to set in the terminals title bar. + +If ```title``` is missing shuttle uses the menu's name and sets this as ```title``` ## Roadmap diff --git a/Shuttle/AppDelegate.h b/Shuttle/AppDelegate.h index 2a4783a..916084c 100644 --- a/Shuttle/AppDelegate.h +++ b/Shuttle/AppDelegate.h @@ -15,16 +15,21 @@ NSStatusItem *statusItem; NSString *shuttleConfigFile; - NSString *shuttleJSONPath; // This is for the JSON File NSDate *configModified; NSDate *sshConfigUser; NSDate *sshConfigSystem; - NSString *terminalPref; - NSString *editorPref; - NSString *iTermVersion; + //Global settings Pref in the JSON file. + NSString *shuttleJSONPathPref; //alternate path the JSON file + NSString *terminalPref; //which terminal will we be using iTerm or Terminal.app + NSString *editorPref; //what app opens the JSON fiile vi, nano... + NSString *iTermVersionPref; //which version of iTerm nightly or stable + NSString *openInPref; //by default are commands opened in tabs or new windows. + NSString *themePref; //The global theme. + + //used to gather ssh config settings NSMutableArray* shuttleHosts; NSMutableArray* ignoreHosts; NSMutableArray* ignoreKeywords; diff --git a/Shuttle/AppDelegate.m b/Shuttle/AppDelegate.m index c1e2eb9..af7acee 100644 --- a/Shuttle/AppDelegate.m +++ b/Shuttle/AppDelegate.m @@ -11,13 +11,13 @@ @implementation AppDelegate - (void) awakeFromNib { // The location for the JSON path file. This is a simple file that contains the hard path to the *.json settings file. - shuttleJSONPath = [NSHomeDirectory() stringByAppendingPathComponent:@".shuttle.path"]; + shuttleJSONPathPref = [NSHomeDirectory() stringByAppendingPathComponent:@".shuttle.path"]; //if file shuttle.path exists in ~/.shuttle.path then read this file as it should contain the custom path to *.json - if( [[NSFileManager defaultManager] fileExistsAtPath:shuttleJSONPath] ) { + if( [[NSFileManager defaultManager] fileExistsAtPath:shuttleJSONPathPref] ) { //Read the shuttle.path file which contains the path to the json file - NSString *jsonConfigPath = [NSString stringWithContentsOfFile:shuttleJSONPath encoding:NSUTF8StringEncoding error:NULL]; + NSString *jsonConfigPath = [NSString stringWithContentsOfFile:shuttleJSONPathPref encoding:NSUTF8StringEncoding error:NULL]; //Remove the white space if any. jsonConfigPath = [ jsonConfigPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; @@ -196,7 +196,9 @@ - (void) loadMenu { terminalPref = [json[@"terminal"] lowercaseString]; editorPref = [json[@"editor"] lowercaseString]; - iTermVersion = [json[@"iTerm_version"] lowercaseString]; + iTermVersionPref = [json[@"iTerm_version"] lowercaseString]; + openInPref = [json[@"open_in"] lowercaseString]; + themePref = json[@"default_theme"]; launchAtLoginController.launchAtLogin = [json[@"launch_at_login"] boolValue]; shuttleHosts = json[@"hosts"]; ignoreHosts = json[@"ssh_config_ignore_hosts"]; @@ -372,6 +374,10 @@ - (void) openHost:(NSMenuItem *) sender { //NSLog(@"sender: %@", sender); //NSLog(@"Command: %@",[sender representedObject]); + NSString *errorMessage; + NSString *errorInfo; + + //Place the comma delimited string of menu item settings into an array NSArray *objectsFromJSON = [[sender representedObject] componentsSeparatedByString:(@",")]; @@ -384,35 +390,53 @@ - (void) openHost:(NSMenuItem *) sender { //Are commands run in a new tab (default) a new terminal window (new), or in the current tab of the last used window (current). NSString *terminalWindow; + escapedObject = [[objectsFromJSON objectAtIndex:0] stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; - //if for some reason we get a representedObject with only one item... - if (objectsFromJSON.count <=1) { - escapedObject = [[sender representedObject] stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; - } - else { - escapedObject = [[objectsFromJSON objectAtIndex:0] stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; - //Check if terminalTheme is null - if( [[objectsFromJSON objectAtIndex:1] isEqualToString:@"(null)"] ){ + //if terminalTheme is not set then check for a global setting. + if( [[objectsFromJSON objectAtIndex:1] isEqualToString:@"(null)"] ){ + if(themePref == 0) { if( [terminalPref isEqualToString:@"iterm"] ){ + //we have no global theme and there is no theme in the command settings. + //Forcing the Default profile for iTerm and the basic profile for Terminal.app terminalTheme = @"Default"; - }else{ - terminalTheme = @"basic"; - } + }else{ + terminalTheme = @"basic"; + } + //We have a global setting using this as the theme. }else { - terminalTheme = [objectsFromJSON objectAtIndex:1]; + terminalTheme = themePref; } - //Check if terminalTitle is null - if( [[objectsFromJSON objectAtIndex:2] isEqualToString:@"(null)"]){ - terminalTitle = [objectsFromJSON objectAtIndex:4]; - }else{ - terminalTitle = [objectsFromJSON objectAtIndex:2]; - } - //Check if inTerminal is null - if( [[objectsFromJSON objectAtIndex:3] isEqualToString:@"(null)"]){ - terminalWindow = @"default"; //this is not currently used. - }else{ - terminalWindow = [objectsFromJSON objectAtIndex:3]; + //we have command level theme override the Global default_theme settings. + }else{ + terminalTheme = [objectsFromJSON objectAtIndex:1]; + } + + //Check if terminalTitle is null + if( [[objectsFromJSON objectAtIndex:2] isEqualToString:@"(null)"]){ + //setting the empty title to that of the menu item. + terminalTitle = [objectsFromJSON objectAtIndex:4]; + }else{ + terminalTitle = [objectsFromJSON objectAtIndex:2]; + } + + //Check if inTerminal is null if so then use the default settings of open_in + if( [[objectsFromJSON objectAtIndex:3] isEqualToString:@"(null)"]){ + + //if open_in is not "tab" or "new" then force the default of "tab". + if( ![openInPref isEqualToString:@"tab"] && ![openInPref isEqualToString:@"new"]){ + openInPref = @"tab"; } + //open_in was not empty or bad value we are passing the settings. + terminalWindow = openInPref; + }else{ + //inTerminal is not null and overrides the default values of open_in + terminalWindow = [objectsFromJSON objectAtIndex:3]; + if( ![terminalWindow isEqualToString:@"new"] && ![terminalWindow isEqualToString:@"current"] && ![terminalWindow isEqualToString:@"tab"]) + { + errorMessage = [NSString stringWithFormat:@"%@%@%@ %@",@"'",terminalWindow,@"'", @"is not a valid value for inTerminal. Please fix this in the JSON file"]; + errorInfo = @"bad \"inTerminal\":\"VALUE\" in the JSON settings"; + [self throwError:errorMessage additionalInfo:errorInfo continueOnErrorOption:NO]; + } } //Set Paths to iTerm Stable AppleScripts @@ -445,56 +469,74 @@ - (void) openHost:(NSMenuItem *) sender { //If the JSON file is set to use iTerm else if ( [terminalPref rangeOfString: @"iterm"].location !=NSNotFound ) { - //If the JSON file is set to use applescript via iTermVersion then configure for iTerm Stable - if( [iTermVersion isEqualToString: @"stable"] ) { - //run the applescript that works with iTerm Stable - + //If the JSON prefs for iTermVersion are not stable or nightly throw an error + if( ![iTermVersionPref isEqualToString: @"stable"] && ![iTermVersionPref isEqualToString:@"nightly"] ) { + + if( iTermVersionPref == 0 ) { + errorMessage = @"\"iTerm_version\": \"VALUE\", is missing.\n\"VALUE\" can be \"stable\" or \"nightly\"\n\nPlease fix your shuttle JSON settings.\nSee readme.md on shuttle's github for help."; + errorInfo = @"Press Continue to try iTerm stable applescripts.\n -->(not recommended)<--\nThis will fail if you have iTerm nightly installed.\n\nPlease fix the JSON settings.\nPress Quit to exit shuttle."; + [self throwError:errorMessage additionalInfo:errorInfo continueOnErrorOption:YES]; + iTermVersionPref = @"stable"; + + }else{ + errorMessage = [NSString stringWithFormat:@"%@%@%@ %@",@"'",iTermVersionPref,@"'", @"is not a valid value for iTerm_version. Please fix this in the JSON file"]; + errorInfo = @"bad \"iTerm_version\": \"VALUE\" in the JSON settings"; + [self throwError:errorMessage additionalInfo:errorInfo continueOnErrorOption:NO]; + } + } + + if( [iTermVersionPref isEqualToString:@"stable"]) { + + //run the applescript that works with iTerm Stable //if we are running in a new iTerm "Stable" Window if ( [terminalWindow isEqualToString:@"new"] ) { [self runScript:iTermStableNewWindow handler:handlerName parameters:passParameters]; - }else { - //if we are running in the current iTerm "Stable" Window - if ( [terminalWindow isEqualToString:@"current"] ) { - [self runScript:iTermStableCurrentWindow handler:handlerName parameters:passParameters]; - }else { - //we are using the default action of shuttle, use the active window in a new Tab - [self runScript:iTermStableNewTabDefault handler:handlerName parameters:passParameters]; - } - } + } + //if we are running in the current iTerm "Stable" Window + if ( [terminalWindow isEqualToString:@"current"] ) { + [self runScript:iTermStableCurrentWindow handler:handlerName parameters:passParameters]; + } + //we are using the default action of shuttle... The active window in a new tab + if ( [terminalWindow isEqualToString:@"tab"] ) { + [self runScript:iTermStableNewTabDefault handler:handlerName parameters:passParameters]; + } } //iTermVersion is not set to "stable" using applescripts Configured for Nightly - else { + if( [iTermVersionPref isEqualToString:@"nightly"]) { //if we are running in a new iTerm "Nightly" Window - if( [terminalWindow isEqualToString:@"new"] ) { + if ( [terminalWindow isEqualToString:@"new"] ) { [self runScript:iTerm2NightlyNewWindow handler:handlerName parameters:passParameters]; - - }else { - //if we are running in the current iTerm "Nightly" Window - if( [terminalWindow isEqualToString:@"current"] ) { - [self runScript:iTerm2NightlyCurrentWindow handler:handlerName parameters:passParameters]; - }else { - //we are using the default action of shuttle, use the active window in a new Tab - [self runScript:iTerm2NightlyNewTabDefault handler:handlerName parameters:passParameters]; - } - } + } + //if we are running in the current iTerm "Nightly" Window + if ( [terminalWindow isEqualToString:@"current"] ) { + [self runScript:iTerm2NightlyCurrentWindow handler:handlerName parameters:passParameters]; + } + //we are using the default action of shuttle... The active window in a new tab + if ( [terminalWindow isEqualToString:@"tab"] ) { + [self runScript:iTerm2NightlyNewTabDefault handler:handlerName parameters:passParameters]; + } } } - //If JSON file is set to use Terminal.app + //If JSON settings are set to use Terminal.app else { + //if we are running in a new terminal Window if ( [terminalWindow isEqualToString:@"new"] ) { [self runScript:terminalNewWindow handler:handlerName parameters:passParameters]; - }else { - if ( [terminalWindow isEqualToString:@"current"] ) { - [self runScript:terminalCurrentWindow handler:handlerName parameters:passParameters]; - }else { - [self runScript:terminalNewTabDefault handler:handlerName parameters:passParameters]; - } - } + } + + //if we are running in the current terminal Window + if ( [terminalWindow isEqualToString:@"current"] ) { + [self runScript:terminalCurrentWindow handler:handlerName parameters:passParameters]; + } + + //we are using the default action of shuttle... The active window in a new tab + if ( [terminalWindow isEqualToString:@"tab"] ) { + [self runScript:terminalNewTabDefault handler:handlerName parameters:passParameters]; + } } } -- (void) runScript:(NSString *)scriptPath handler:(NSString*)handlerName parameters:(NSArray*)parametersInArray -{ +- (void) runScript:(NSString *)scriptPath handler:(NSString*)handlerName parameters:(NSArray*)parametersInArray { //special thanks to stackoverflow.com/users/316866/leandro for pointing me the right direction. //see http://goo.gl/olcpaX NSAppleScript * appleScript; @@ -568,6 +610,25 @@ - (IBAction)showImportPanel:(id)sender { } +-(void) throwError:(NSString*)errorMessage additionalInfo:(NSString*)errorInfo continueOnErrorOption:(BOOL)continueOption { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setInformativeText:errorInfo]; + [alert setMessageText:errorMessage]; + [alert setAlertStyle:NSWarningAlertStyle]; + + if (continueOption) { + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Continue"]; + + }else{ + [alert addButtonWithTitle:@"Quit"]; + } + + if ([alert runModal] == NSAlertFirstButtonReturn) { + [NSApp terminate:NSApp]; + } +} + - (IBAction)showExportPanel:(id)sender { NSSavePanel * savePanelObj = [NSSavePanel savePanel]; //Display the Save Panel @@ -590,11 +651,14 @@ - (IBAction)configure:(id)sender { //build the editor command NSString *editorCommand = [NSString stringWithFormat:@"%@ %@", editorPref, shuttleConfigFile]; + //build the reprensented object. It's expecting menuCmd, termTheme, termTitle, termWindow, menuName + NSString *editorRepObj = [NSString stringWithFormat:@"%@,%@,%@,%@,%@", editorCommand, nil, @"Editing shuttle JSON", nil, nil]; + //make a menu item for the command selector(openHost:) runs in a new terminal window. NSMenuItem *editorMenu = [[NSMenuItem alloc] initWithTitle:@"editJSONconfig" action:@selector(openHost:) keyEquivalent:(@"")]; //set the command for the menu item - [editorMenu setRepresentedObject:editorCommand]; + [editorMenu setRepresentedObject:editorRepObj]; //open the JSON file in the terminal editor. [self openHost:editorMenu]; diff --git a/Shuttle/Shuttle-Info.plist b/Shuttle/Shuttle-Info.plist index 2080b6b..df43b6f 100644 --- a/Shuttle/Shuttle-Info.plist +++ b/Shuttle/Shuttle-Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.2.4 + 1.2.5 CFBundleSignature ???? CFBundleVersion - 1.2.4 + 1.2.5 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/Shuttle/shuttle.default.json b/Shuttle/shuttle.default.json index a3fa14d..5259821 100644 --- a/Shuttle/shuttle.default.json +++ b/Shuttle/shuttle.default.json @@ -6,81 +6,53 @@ "For more information on how to configure, please see http://fitztrev.github.io/shuttle/" ], "editor": "default", - "iTerm_version": "nightly", "launch_at_login": false, - "show_ssh_config_hosts": true, + "terminal": "Terminal.app", + "iTerm_version": "nightly", + "default_theme": "Homebrew", + "open_in": "new", + "show_ssh_config_hosts": false, "ssh_config_ignore_hosts": [ ], "ssh_config_ignore_keywords": [ ], - "terminal": "iTerm", "hosts": [ { - "cmd": "ps aux | grep foo", - "inTerminal": "new", - "name": "look for process foo", - "theme": "Default", - "title": "grep foo" + "cmd": "ps aux | grep defaults", + "name": "Grep - Opens in Default-window-theme-title" }, { "Spouses Servers": [ { - "cmd": "echo '—->WARNING<-- Running a command in this active terminal! Are you sure? y/n'; read sure; if [ $sure == y ]; then echo running command... && tail -f /var/log/messages; else echo exiting...; fi", + "cmd": "echo '—->WARNING<-- Running a command in this active terminal! Are you sure? y/n'; read sure; if [ $sure == y ]; then echo running command... && tail -f /var/log/current; else echo exiting...; fi", "inTerminal": "current", - "name": "Look at the Logs" + "name": "Logs - Opens in the current active terminal window" }, { "Jane’s Servers": [ { "cmd": "ssh username@blog2.example.com", - "name": "Wordpress Box" + "inTerminal": "tab", + "name": "SSH blog - Opens in Tab of active window", + "theme": "basic", + "title": "title of tab" }, - { + { "cmd": "ssh username@shop1.example.com", - "name": "Shopping Site" + "inTerminal": "new", + "name": "SSH Shop - Opens in New Window", + "theme": "basic", + "title": "title of new window" } ] } ] }, - { - "Work": [ - { - "cmd": "ssh username@dev.example.net -p 4000", - "name": "dev.example.net" - }, - { - "cmd": "ssh username@staging.example.net -p 5000", - "name": "staging.example.net" - }, - { - "cmd": "ssh username@example.net -p 6000", - "name": "production.example.net" - } - ] - }, { "Vagrant": [ { "cmd": "ssh vagrant@127.0.0.1 -p 2222 -i ~/.vagrant.d/insecure_private_key", - "name": "precise32" + "name": "Vagrant - Opens in default-window-theme-title" } ] } ] -} - - - - - - - - - - - - - - - - - \ No newline at end of file +} \ No newline at end of file