Permalink
Browse files

Add built-in editors.json, merge with external editors

  • Loading branch information...
1 parent 039edb2 commit a9a70a36f159a2cd55b643c99a87e25c961fd6dd @andreyvit andreyvit committed Jul 19, 2013
@@ -4,8 +4,14 @@ extern NSString *const LRErrorDomain;
enum {
LRErrorSandboxedTasksNotSupportedBefore10_8,
+ LRErrorJsonParsingError,
LRErrorPluginNotReadable,
LRErrorPluginNotExecutable,
LRErrorPluginApiViolation,
LRErrorEditorPluginReturnedBrokenState,
};
+
+#define return_error(returnValue, outError, error) do { \
+ if (outError) *outError = error; \
+ return nil; \
+ } while(0)
@@ -1,5 +1,6 @@
#import <Foundation/Foundation.h>
+#import "ArrayDiff.h"
typedef enum {
EditorStateNotFound,
@@ -9,10 +10,10 @@ typedef enum {
} EditorState;
-@interface Editor : NSObject
+@interface Editor : NSObject <ObjectWithAttributes>
-@property(nonatomic, readonly, copy) NSString *identifier;
-@property(nonatomic, readonly, copy) NSString *displayName;
+@property(nonatomic, copy) NSString *identifier;
+@property(nonatomic, copy) NSString *displayName;
@property(nonatomic, readonly, assign) EditorState state;
@property(nonatomic, readonly, assign, getter=isStateStale) BOOL stateStale;
@property(nonatomic, readonly, assign, getter=isRunning) BOOL running;
@@ -19,7 +19,8 @@ @interface Editor ()
@end
@implementation Editor
-
+@synthesize identifier = _identifier;
+@synthesize displayName = _displayName;
@synthesize state = _state;
@synthesize stateStale = _stateStale;
@synthesize mruPosition = _mruPosition;
@@ -30,18 +31,12 @@ - (id)init
self = [super init];
if (self) {
_mruPosition = NSNotFound;
+
+ [self updateStateSoon];
}
return self;
}
-- (NSString *)identifier {
- MustOverride();
-}
-
-- (NSString *)displayName {
- MustOverride();
-}
-
- (BOOL)jumpToFile:(NSString *)file line:(NSInteger)line {
MustOverride();
}
@@ -50,11 +45,20 @@ - (BOOL)isRunning {
return self.state == EditorStateRunning;
}
+- (void)setAttributesDictionary:(NSDictionary *)attributes {
+ self.identifier = attributes[@"id"];
+ self.displayName = attributes[@"name"];
+
+ [self updateStateSoon];
+}
+
- (void)updateStateSoon {
if (self.stateStale)
return;
self.stateStale = YES;
- [self doUpdateStateInBackground];
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
+ [self doUpdateStateInBackground];
+ });
}
- (void)updateState:(EditorState)state error:(NSError *)error {
@@ -1,6 +1,7 @@
#import "EditorManager.h"
#import "ATSandboxing.h"
+#import "ATFunctionalStyle.h"
#import "RegexKitLite.h"
#import "ExternalEditor.h"
@@ -52,25 +53,32 @@ - (Editor *)activeEditor {
return nil;
}
+- (NSArray *)loadBundledEditors {
+ NSURL *editorsFileURL = [[NSBundle mainBundle] URLForResource:@"editors.json" withExtension:nil];
+ if (!editorsFileURL)
+ abort();
+ NSError *error = nil;
+ NSDictionary *editorsData = [NSDictionary LR_dictionaryWithContentsOfJSONFileURL:editorsFileURL error:nil];
+ NSAssert1(editorsData, @"Failed to parse editors.json: %@", error.localizedDescription);
+ return editorsData[@"editors"];
+}
+
+- (NSArray *)loadExternalEditors {
+ return [LRFindPluginsInFolder([ATUserScriptsDirectoryURL() URLByAppendingPathComponent:@"Editors"], @[@"editor-v1"]) arrayByMappingElementsUsingBlock:^id(SingleFilePlugin *script) {
+ return [script.properties[@"editor"] dictionaryByAddingEntriesFromDictionary:@{@"script": script}];
+ }];
+}
+
- (void)updateEditors {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
- NSArray *plugins = LRFindPluginsInFolder([ATUserScriptsDirectoryURL() URLByAppendingPathComponent:@"Editors"], @[@"editor v1"]);
+ NSArray *plugins1 = [self loadBundledEditors];
+ NSArray *plugins2 = [self loadExternalEditors];
+ NSArray *plugins = [plugins1 arrayByMergingDictionaryValuesGroupedByKeyPath:@"id" withArray:plugins2];
+
dispatch_async(dispatch_get_main_queue(), ^{
- ArrayDiffWithKeyCallbacks(_editors, plugins, ^id(id object) {
- return ((ExternalEditor *)object).script.scriptFileURL;
- }, ^id(id object) {
- SingleFilePlugin *script = (SingleFilePlugin *)object;
- return script.scriptFileURL;
- }, ^(id newObject) {
- SingleFilePlugin *script = (SingleFilePlugin *)newObject;
- Editor *editor = [[ExternalEditor alloc] initWithScript:script];
- [_editors addObject:editor];
- }, ^(id oldObject) {
- [_editors removeObject:oldObject];
- }, ^(id oldObject, id newObject) {
- ExternalEditor *editor = (ExternalEditor *)oldObject;
- [editor updateScript:(SingleFilePlugin *)newObject];
- });
+ [ModelDiffs updateMutableObjectsArray:_editors usingAttributesPropertyWithNewAttributeValueDictionaries:plugins identityKeyPath:@"identifier" identityAttributeKey:@"id" create:^(NSDictionary *attributes) {
+ return [[ExternalEditor alloc] init];
+ }];
for (Editor *editor in _editors) {
[editor updateStateSoon];
}
@@ -4,10 +4,6 @@
@interface ExternalEditor : Editor
-- (id)initWithScript:(SingleFilePlugin*)aScript;
-
-@property(nonatomic, readonly, strong) SingleFilePlugin *script;
-
-- (void)updateScript:(SingleFilePlugin *)script;
+@property(nonatomic, strong) SingleFilePlugin *script;
@end
@@ -10,33 +10,11 @@ @implementation ExternalEditor
@synthesize script = _script;
-- (id)initWithScript:(SingleFilePlugin*)aScript {
- self = [super init];
- if (self) {
- _script = [aScript retain];
- [self updateDerivedProperties];
- }
- return self;
-}
-
-- (NSString *)identifier {
- return self.script.properties[@"editor-id"];
-}
-
-- (NSString *)displayName {
- return self.script.properties[@"editor-name"];
-}
-
-- (void)updateScript:(SingleFilePlugin *)script {
- if ([_script updateProperties:script.properties]) {
- [self updateDerivedProperties];
- [self updateStateSoon];
- }
-}
+- (void)setAttributesDictionary:(NSDictionary *)attributes {
+ [super setAttributesDictionary:attributes];
-- (void)updateDerivedProperties {
- // gives 0 for missing keys, which is exactly what we want
- self.defaultPriority = [self.script.properties[@"editor-default-priotity"] integerValue];
+ self.defaultPriority = [attributes[@"default-priotity"] integerValue]; // gives 0 for missing keys, which is exactly what we want
+ self.script = attributes[@"script"];
}
- (void)doUpdateStateInBackground {
@@ -6,6 +6,11 @@
@class SingleFilePlugin;
+@interface NSDictionary (LRPluginCommons)
+
++ (NSDictionary *)LR_dictionaryWithContentsOfJSONFileURL:(NSURL *)fileURL error:(NSError **)error;
+
+@end
NSDictionary *LRExtractMetadata(NSString *content);
NSDictionary *LRExtractFileMetadata(NSURL *file);
NSArray *LRFindPluginsInFolder(NSURL *folder, NSArray *validApiValues);
@@ -1,16 +1,60 @@
#import "LRPluginCommons.h"
#import "RegexKitLite.h"
+#import "Errors.h"
+
+@implementation NSDictionary (LRPluginCommons)
+
++ (NSDictionary *)LR_dictionaryWithContentsOfJSONFileURL:(NSURL *)fileURL error:(NSError **)outError {
+ NSError *error = nil;
+ NSData *raw = [NSData dataWithContentsOfURL:fileURL options:0 error:&error];
+ if (!raw)
+ return_error(nil, outError, error);
+
+ id object = [NSJSONSerialization JSONObjectWithData:raw options:0 error:&error];
+ if (!object)
+ return_error(nil, outError, error);
+
+ if (![object isKindOfClass:[NSDictionary class]])
+ return_error(nil, outError, [NSError errorWithDomain:LRErrorDomain code:LRErrorJsonParsingError userInfo:nil]);
+
+ return object;
+}
+
+@end
+
+
+void LRSetDottedKey(NSMutableDictionary *dictionary, NSString *key, id value) {
+ NSRange range = [key rangeOfString:@"."];
+ if (range.location == NSNotFound) {
+ dictionary[key] = value;
+ } else {
+ NSString *folderKey = [key substringToIndex:range.location];
+ NSString *subkey = [key substringFromIndex:range.location + range.length];
+
+ NSMutableDictionary *folder;
+ id folderRaw = dictionary[folderKey];
+ if ([folderRaw isKindOfClass:[NSMutableDictionary class]])
+ folder = folderRaw;
+ else if ([folderRaw isKindOfClass:[NSDictionary class]])
+ dictionary[folderKey] = folder = [folderRaw mutableCopy];
+ else
+ dictionary[folderKey] = folder = [[NSMutableDictionary alloc] init];
+
+ LRSetDottedKey(folder, subkey, value);
+ }
+}
+
NSDictionary *LRExtractMetadata(NSString *content) {
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
-
- [content enumerateStringsMatchedByRegex:@"^(?:#|//|--)\\s*LR-([a-zA-Z0-9-]+):(.*)$" options:RKLMultiline inRange:NSMakeRange(0, NSUIntegerMax) error:nil enumerationOptions:0 usingBlock:^(NSInteger captureCount, NSString *const *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
+ [properties setValue:@"foo" forKeyPath:@"bar.boz"];
+ [content enumerateStringsMatchedByRegex:@"^(?:[^a-z\\s]*)\\s*LR ([a-zA-Z0-9.-]+)\\s*:(.*)$" options:RKLMultiline|RKLCaseless inRange:NSMakeRange(0, NSUIntegerMax) error:nil enumerationOptions:0 usingBlock:^(NSInteger captureCount, NSString *const *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
// NSLog(@"Match: %@ ::: %@ ::: %@", capturedStrings[0], capturedStrings[1], capturedStrings[2]);
NSString *key = [capturedStrings[1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *value = [capturedStrings[2] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
- properties[key] = value;
+ LRSetDottedKey(properties, key, value);
}];
return properties;
@@ -54,12 +98,12 @@
NSDictionary *attrs = [item resourceValuesForKeys:@[NSURLIsRegularFileKey, NSURLIsSymbolicLinkKey, NSURLIsReadableKey, NSURLIsExecutableKey] error:nil];
if ([attrs[NSURLIsRegularFileKey] boolValue] || [attrs[NSURLIsSymbolicLinkKey ] boolValue]) {
NSDictionary *props = LRExtractFileMetadata(item);
- if (!props[@"plugin-api"]) {
- NSLog(@"LRFindPluginsInFolder: Not a plugin (missing LR-plugin-api key): %@", item);
+ if (!props[@"api"]) {
+ NSLog(@"LRFindPluginsInFolder: Not a plugin (missing LR api key): %@", item);
continue;
}
- if (![validApiValues containsObject:props[@"plugin-api"]]) {
- NSLog(@"LRFindPluginsInFolder: Unsupported LR-plugin-api value '%@' in '%@', looking for LR-plugin-api values: %@", props[@"plugin-api"], item, [validApiValues componentsJoinedByString:@", or "]);
+ if (![validApiValues containsObject:props[@"api"]]) {
+ NSLog(@"LRFindPluginsInFolder: Unsupported api value '%@' in '%@', looking for api values: %@", props[@"api"], item, [validApiValues componentsJoinedByString:@", or "]);
continue;
}
NSLog(@"LRFindPluginsInFolder: Props of %@: %@", item, props);
@@ -28,8 +28,10 @@
@interface NSArray (ATFunctionalStyleAdditions)
- (NSDictionary *)dictionaryWithElementsGroupedByKeyPath:(NSString *)keyPath;
+- (NSDictionary *)dictionaryWithElementsGroupedByBlock:(id(^)(id value))block;
- (NSDictionary *)dictionaryWithElementsMultiGroupedByKeyPath:(NSString *)keyPath;
+- (NSDictionary *)dictionaryWithElementsMultiGroupedByBlock:(NSArray *(^)(id value))block;
- (NSSet *)setWithElementsGroupedByKeyPath:(NSString *)keyPath;
@@ -39,14 +41,25 @@
- (NSArray *)filteredArrayUsingBlock:(BOOL(^)(id value))block;
+- (NSArray *)arrayByMergingDictionaryValuesGroupedByKeyPath:(NSString *)keyPath withArray:(NSArray *)peer;
+
@end
@interface NSDictionary (ATFunctionalStyleAdditions)
+- (NSDictionary *)dictionaryByReversingKeysAndValues;
+
- (NSDictionary *)dictionaryByMappingKeysToSelector:(SEL)selector;
- (NSDictionary *)dictionaryByMappingValuesToSelector:(SEL)selector;
- (NSDictionary *)dictionaryByMappingValuesToSelector:(SEL)selector withObject:(id)object;
- (NSDictionary *)dictionaryByMappingValuesToKeyPath:(NSString *)valueKeyPath;
+- (NSDictionary *)dictionaryByMappingValuesToBlock:(id(^)(id key, id value))block;
+
+- (NSDictionary *)dictionaryByMappingValuesAccordingToSchema:(NSDictionary *)schema;
+
+- (NSDictionary *)dictionaryByAddingEntriesFromDictionary:(NSDictionary *)peer;
+
+- (NSDictionary *)dictionaryByMergingDictionaryValuesWithDictionary:(NSDictionary *)peer;
@end
Oops, something went wrong.

0 comments on commit a9a70a3

Please sign in to comment.