Skip to content

Commit

Permalink
Clean pull request #45
Browse files Browse the repository at this point in the history
  • Loading branch information
kasper committed Nov 19, 2015
1 parent 3bbd75c commit ca11ec6
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 54 deletions.
39 changes: 28 additions & 11 deletions API.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
JavaScript API
==============

This documentation is an overview of the JavaScript API provided by Phoenix. Use this as a guide for writing your window management script. Your script should reside in `~/.phoenix.js`. Phoenix includes [Underscore.js](http://underscorejs.org) (1.8.3) — you can use its features in your configuration. Underscore provides useful helpers for handling JavaScript functions and objects.

You may add JavaScript pre-processing to your `~/.phoenix.js` file by adding a [Shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) to the beginning of your file. For example, use [Babel](https://babeljs.io/) to pre-process ES2015 JavaScript syntax:
```javascript
#!/usr/bin/env babel
const handlers = [];
handlers.push(Phoenix.bind('c', ['alt', 'shift'], () => {
const app = App.launch('Google Chrome');
app.focus();
}));
```
This documentation is an overview of the JavaScript API provided by Phoenix. Use this as a guide for writing your window management script. Your script should reside in `~/.phoenix.js`. Phoenix includes [Underscore.js](http://underscorejs.org) (1.8.3) — you can use its features in your configuration. Underscore provides useful helpers for handling JavaScript functions and objects. You may also use JavaScript [preprocessing](#preprocessing) and use languages such as CoffeeScript to write your Phoenix-configuration.

## API

Expand Down Expand Up @@ -67,6 +57,33 @@ var handler = Phoenix.on('screensDidChange', function () {});

Your configuration file is loaded when the app launches. All functions are evaluated (and executed if necessary) when this happens. Phoenix also reloads the configuration when any changes are detected to the file. You may also reload the configuration manually from the status bar or programmatically from your script.

## Preprocessing

You may add JavaScript preprocessing to your configuration by adding a [Shebang](https://en.wikipedia.org/wiki/Shebang_(Unix))-directive to the beginning of your file. For example, use [CoffeeScript](http://coffeescript.org) to write your configuration:

```coffeescript
#!/usr/bin/env coffee

keys = []

keys.push Phoenix.bind 's', [ 'ctrl', 'shift' ], ->

App.launch('Safari').focus()
```

Or use [Babel](http://babeljs.io) to use ECMAScript 6 JavaScript:

```javascript
#!/usr/bin/env babel

const keys = [];

keys.push(Phoenix.bind('s', [ 'ctrl', 'shift' ], () => {

App.launch('Safari').focus();
}));
```

## 1. Keys

All valid keys for binding are as follows:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Release: dd.mm.yyyy
### New

- Phoenix now supports events! See the [API](API.md#2-events). To bind an event to a callback function, you call the `on`-function for the `Phoenix`-object.
- You may now use JavaScript [preprocessing](API.md#preprocessing) and use languages such as CoffeeScript to write your Phoenix-configuration (#45). Thanks @shayne!

### Changes

Expand Down
32 changes: 21 additions & 11 deletions Phoenix.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
objects = {

/* Begin PBXBuildFile section */
1C93D6641BE11FF100649405 /* PHScriptHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C93D6631BE11FF100649405 /* PHScriptHelper.m */; settings = {ASSET_TAGS = (); }; };
1C93D6641BE11FF100649405 /* PHShebangPreprocessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C93D6631BE11FF100649405 /* PHShebangPreprocessor.m */; };
A7183B671B6E865F00842E13 /* PHKeyHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A7183B661B6E865F00842E13 /* PHKeyHandler.m */; };
A72EAD041B5CE34800DD537B /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = A72EAD031B5CE34800DD537B /* Credits.rtf */; };
A72EAD071B5D00B800DD537B /* PHModalWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = A72EAD061B5D00B800DD537B /* PHModalWindowController.m */; };
A72ED7B61B6E25940064E35B /* PHPhoenix.m in Sources */ = {isa = PBXBuildFile; fileRef = A72ED7B51B6E25940064E35B /* PHPhoenix.m */; };
A73C2CA81B9078A30004C663 /* PHCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = A73C2CA71B9078A30004C663 /* PHCommand.m */; };
A73C2CAB1B9088040004C663 /* PHNotificationHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = A73C2CAA1B9088040004C663 /* PHNotificationHelper.m */; };
A741336C1BB7EEAC008DAF39 /* PHHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A741336B1BB7EEAC008DAF39 /* PHHandler.m */; settings = {ASSET_TAGS = (); }; };
A741336F1BB7F228008DAF39 /* PHEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A741336E1BB7F228008DAF39 /* PHEventHandler.m */; settings = {ASSET_TAGS = (); }; };
A74133721BB7F556008DAF39 /* PHEventTranslator.m in Sources */ = {isa = PBXBuildFile; fileRef = A74133711BB7F556008DAF39 /* PHEventTranslator.m */; settings = {ASSET_TAGS = (); }; };
A741336C1BB7EEAC008DAF39 /* PHHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A741336B1BB7EEAC008DAF39 /* PHHandler.m */; };
A741336F1BB7F228008DAF39 /* PHEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A741336E1BB7F228008DAF39 /* PHEventHandler.m */; };
A74133721BB7F556008DAF39 /* PHEventTranslator.m in Sources */ = {isa = PBXBuildFile; fileRef = A74133711BB7F556008DAF39 /* PHEventTranslator.m */; };
A762478A1B81FD3C00ECF209 /* PHAXUIElement.m in Sources */ = {isa = PBXBuildFile; fileRef = A76247891B81FD3C00ECF209 /* PHAXUIElement.m */; };
A79B89E01B5E74C1004DDE82 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = A79B89DE1B5E74C1004DDE82 /* MainMenu.xib */; };
A79B89E21B5E75CA004DDE82 /* ModalWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = A79B89E11B5E75CA004DDE82 /* ModalWindow.xib */; };
Expand All @@ -34,8 +34,8 @@
A79C46AA1B5BF41900C460CF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A79C46A91B5BF41900C460CF /* Images.xcassets */; };
A7D1D9A91B6FD6A300E00CC9 /* PHKeyTranslator.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D1D9A81B6FD6A300E00CC9 /* PHKeyTranslator.m */; };
A7D1F7651B5BFF5B0052E646 /* PhoenixTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D1F7641B5BFF5B0052E646 /* PhoenixTests.m */; };
A7E6DF6C1BBAB5B8001920B4 /* PHAXObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = A7E6DF6B1BBAB5B8001920B4 /* PHAXObserver.m */; settings = {ASSET_TAGS = (); }; };
A7E6DF6F1BBAC1F3001920B4 /* PHAccessibilityObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = A7E6DF6E1BBAC1F3001920B4 /* PHAccessibilityObserver.m */; settings = {ASSET_TAGS = (); }; };
A7E6DF6C1BBAB5B8001920B4 /* PHAXObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = A7E6DF6B1BBAB5B8001920B4 /* PHAXObserver.m */; };
A7E6DF6F1BBAC1F3001920B4 /* PHAccessibilityObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = A7E6DF6E1BBAC1F3001920B4 /* PHAccessibilityObserver.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -49,8 +49,8 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
1C93D6621BE11FF100649405 /* PHScriptHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHScriptHelper.h; sourceTree = "<group>"; };
1C93D6631BE11FF100649405 /* PHScriptHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHScriptHelper.m; sourceTree = "<group>"; };
1C93D6621BE11FF100649405 /* PHShebangPreprocessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHShebangPreprocessor.h; sourceTree = "<group>"; };
1C93D6631BE11FF100649405 /* PHShebangPreprocessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHShebangPreprocessor.m; sourceTree = "<group>"; };
A7183B651B6E865F00842E13 /* PHKeyHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHKeyHandler.h; sourceTree = "<group>"; };
A7183B661B6E865F00842E13 /* PHKeyHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHKeyHandler.m; sourceTree = "<group>"; };
A72A6B031B934C2000F2C4EF /* PHIdentifiableJSExport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PHIdentifiableJSExport.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -106,6 +106,7 @@
A7E6DF6B1BBAB5B8001920B4 /* PHAXObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHAXObserver.m; sourceTree = "<group>"; };
A7E6DF6D1BBAC1F3001920B4 /* PHAccessibilityObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHAccessibilityObserver.h; sourceTree = "<group>"; };
A7E6DF6E1BBAC1F3001920B4 /* PHAccessibilityObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHAccessibilityObserver.m; sourceTree = "<group>"; };
A7EBD1C21BFDF8CE000D8E5F /* PHPreprocessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PHPreprocessor.h; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -155,8 +156,6 @@
A79C468B1B5BF3C100C460CF /* PHOpenAtLoginHelper.m */,
A79C468E1B5BF3C100C460CF /* PHUniversalAccessHelper.h */,
A79C468F1B5BF3C100C460CF /* PHUniversalAccessHelper.m */,
1C93D6621BE11FF100649405 /* PHScriptHelper.h */,
1C93D6631BE11FF100649405 /* PHScriptHelper.m */,
);
name = Helpers;
sourceTree = "<group>";
Expand Down Expand Up @@ -260,6 +259,7 @@
A77952C61BBE7E0300FC8D38 /* Observers */,
A79C46801B5BF3C100C460CF /* PHAppDelegate.h */,
A79C46811B5BF3C100C460CF /* PHAppDelegate.m */,
A7EBD1C11BFDF7E1000D8E5F /* Preprocessors */,
A79C465A1B5BF31100C460CF /* Supporting Files */,
A73FF10C1BBD8F350024CF97 /* Translators */,
);
Expand Down Expand Up @@ -294,6 +294,16 @@
name = "Supporting Files";
sourceTree = "<group>";
};
A7EBD1C11BFDF7E1000D8E5F /* Preprocessors */ = {
isa = PBXGroup;
children = (
A7EBD1C21BFDF8CE000D8E5F /* PHPreprocessor.h */,
1C93D6621BE11FF100649405 /* PHShebangPreprocessor.h */,
1C93D6631BE11FF100649405 /* PHShebangPreprocessor.m */,
);
name = Preprocessors;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -410,7 +420,7 @@
A74133721BB7F556008DAF39 /* PHEventTranslator.m in Sources */,
A7E6DF6F1BBAC1F3001920B4 /* PHAccessibilityObserver.m in Sources */,
A79C46971B5BF3C100C460CF /* PHApp.m in Sources */,
1C93D6641BE11FF100649405 /* PHScriptHelper.m in Sources */,
1C93D6641BE11FF100649405 /* PHShebangPreprocessor.m in Sources */,
A7D1D9A91B6FD6A300E00CC9 /* PHKeyTranslator.m in Sources */,
A79C46A01B5BF3C100C460CF /* PHWindow.m in Sources */,
A72EAD071B5D00B800DD537B /* PHModalWindowController.m in Sources */,
Expand Down
14 changes: 6 additions & 8 deletions Phoenix/PHContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
#import "PHModalWindowController.h"
#import "PHMouse.h"
#import "PHNotificationHelper.h"
#import "PHScriptHelper.h"
#import "PHPathWatcher.h"
#import "PHPhoenix.h"
#import "PHShebangPreprocessor.h"
#import "PHWindow.h"

@interface PHContext ()
Expand Down Expand Up @@ -119,17 +119,15 @@ - (void) loadScript:(NSString *)path {
NSString *script = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];

if (error) {
NSString *message = [NSString stringWithFormat:
@"Error: Could not read file in path “%@” to string. (%@)", path, error];
[self handleException:message];
NSLog(@"Error: Could not read file in path “%@” to string. (%@)", path, error);
}

NSString *preprocessError;
script = [PHScriptHelper preprocessScriptIfNeeded:script atPath:path errorMessage:&preprocessError];
NSError *preprocessError;
script = [PHShebangPreprocessor process:script atPath:path error:&preprocessError];

if (preprocessError) {
NSString *message = [NSString stringWithFormat:@"Error: Preprocess failed with error: %@", preprocessError];
[self handleException:message];
[self handleException:[NSString stringWithFormat:@"Preprocessing failed. (%@)",
preprocessError.localizedDescription]];
}

[self.context evaluateScript:script];
Expand Down
13 changes: 13 additions & 0 deletions Phoenix/PHPreprocessor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Phoenix is released under the MIT License. Refer to https://github.com/kasper/phoenix/blob/master/LICENSE.md
*/

@import Foundation;

@protocol PHPreprocessor <NSObject>

#pragma mark - Preprocessing

+ (NSString *) process:(NSString *)script atPath:(NSString *)path error:(NSError **)error;

@end
7 changes: 4 additions & 3 deletions Phoenix/PHShebangPreprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

@import Foundation;

@interface PHShebangPreprocessor : NSObject
#import "PHPreprocessor.h"

#pragma mark - Script Preprocessing
static NSString * const PHShebangPreprocessorErrorDomain = @"PHShebangPreprocessorErrorDomain";
static const NSInteger PHShebangPreprocessorErrorCode = -1;

+ (nullable NSString *)preprocessScriptIfNeeded:(nonnull NSString *)script atPath:(NSString * __nonnull)path errorMessage:(NSString * _Nullable * _Nonnull)errorMessage;
@interface PHShebangPreprocessor : NSObject <PHPreprocessor>

@end
47 changes: 26 additions & 21 deletions Phoenix/PHShebangPreprocessor.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,48 @@

@implementation PHShebangPreprocessor

#pragma mark - Script Preprocessing
#pragma mark - Preprocessing

+ (nullable NSString *)preprocessScriptIfNeeded:(nonnull NSString *)script atPath:(NSString * __nonnull)path errorMessage:(NSString * _Nullable * _Nonnull)errorMessage {
+ (NSString *) process:(NSString *)script atPath:(NSString *)path error:(NSError **)error {

NSScanner *scanner = [NSScanner scannerWithString:script];

/* Return early if no shebang "#!" */
if (![scanner scanString:@"#!" intoString:NULL]) {
// Shebang #! was not found
if (![scanner scanString:@"#!" intoString:nil]) {
return script;
}

NSString *preprocessCommand;
[scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:&preprocessCommand];

NSPipe *stdoutPipe = [NSPipe pipe];
NSPipe *stderrPipe = [NSPipe pipe];

NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/bin/sh"];
[task setStandardOutput:stdoutPipe];
[task setStandardError:stderrPipe];
NSPipe *standardOutput = [NSPipe pipe];
NSPipe *standardError = [NSPipe pipe];
NSFileHandle *standardOutputFile = standardOutput.fileHandleForReading;

NSString *command;
[scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:&command];

[task setArguments:@[@"-c", [NSString stringWithFormat:@"%@ %@", preprocessCommand, path]]];
task.launchPath = @"/bin/bash";
task.standardOutput = standardOutput;
task.standardError = standardError;
task.arguments = @[ @"-c", [NSString stringWithFormat:@"%@ %@", command, path] ];

NSFileHandle *stdoutFile = [stdoutPipe fileHandleForReading];
NSFileHandle *stderrFile = [stderrPipe fileHandleForReading];
[task launch];

NSData *errorData = [stderrFile readDataToEndOfFile];
NSData *errorData = [standardError.fileHandleForReading readDataToEndOfFile];

// Command resulted in error
if (errorData.length > 0) {
*errorMessage = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding];

NSString *description = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding];

*error = [NSError errorWithDomain:PHShebangPreprocessorErrorDomain
code:PHShebangPreprocessorErrorCode
userInfo:@{ NSLocalizedDescriptionKey: description }];
}

/* Read past the shebang line to prevent syntax error */
[stdoutFile readDataOfLength:2 + preprocessCommand.length];
// Read past shebang #!
[standardOutputFile readDataOfLength:2 + command.length];

return [[NSString alloc] initWithData:[stdoutFile readDataToEndOfFile] encoding:NSUTF8StringEncoding];
return [[NSString alloc] initWithData:[standardOutputFile readDataToEndOfFile] encoding:NSUTF8StringEncoding];
}

@end

0 comments on commit ca11ec6

Please sign in to comment.