Permalink
Please sign in to comment.
Browse files
made it possible to just give the path to an xcodeproject to appledoc.
it will try to do the right thing :D - it will open Xcode file and look for basic project settings and it will then build a docket and install it Signed-off-by: Dominik Pich <Dominik@pich.info>
- Loading branch information...
Showing
with
320 additions
and 1 deletion.
- +33 −1 Application/GBAppledocApplication.m
- +39 −0 Common/DDXcodeProjectFile.h
- +240 −0 Common/DDXcodeProjectFile.m
- +8 −0 appledoc.xcodeproj/project.pbxproj
@@ -0,0 +1,39 @@ | ||
+// | ||
+// DDXcodeProjectFile.h | ||
+// appledoc | ||
+// | ||
+// Created by Dominik Pich on 9/4/12. | ||
+// Copyright (c) 2012 Gentle Bytes. All rights reserved. | ||
+// | ||
+ | ||
+#import <Foundation/Foundation.h> | ||
+ | ||
+/** @file DDXcodeProjectFile.h */ | ||
+ | ||
+/** | ||
+ * reads an xcode 4 Project file and provides an in memory representation | ||
+ * @warning WHICH cannot be saved at the moment | ||
+ * @warning Does only provide the fundamental project settings right now as that's all I need ATM :D | ||
+ */ | ||
+@interface DDXcodeProjectFile : NSObject | ||
+ | ||
+@property(readonly) NSString *path; | ||
+@property(readonly) NSDictionary *dictionary; | ||
+ | ||
+@property(readonly) NSString *name; | ||
+@property(readonly) NSString *minimumVersion; | ||
+@property(readonly) NSString *company; | ||
+@property(readonly) NSString *projectRoot; | ||
+@property(readonly) NSString *classPrefix; | ||
+@property(readonly) NSString *developmentRegion; | ||
+ | ||
+/** | ||
+ * an array of 1-N dicts for all files found in the project. Each dictionary contains 'path' and 'type' | ||
+ * the path is resolved to an absolute path | ||
+ */ | ||
+@property(readonly) NSArray *files; | ||
+ | ||
++ (id)xcodeProjectFileWithPath:(NSString*)path error:(NSError**)pError; | ||
++ (id)xcodeProjectFileWithDictionary:(NSDictionary*)dict error:(NSError**)pError; | ||
+ | ||
+@end |
@@ -0,0 +1,240 @@ | ||
+// | ||
+// DDXcodeProjectFile.m | ||
+// appledoc | ||
+// | ||
+// Created by Dominik Pich on 9/4/12. | ||
+// Copyright (c) 2012 Gentle Bytes. All rights reserved. | ||
+// | ||
+ | ||
+#import "DDXcodeProjectFile.h" | ||
+ | ||
+@interface DDXcodeProjectFile () { | ||
+ NSMutableArray *_mutableFilesBuffer; | ||
+} | ||
+ | ||
+@property(readwrite) NSString *path; | ||
+@property(readwrite) NSDictionary *dictionary; | ||
+ | ||
+@property(readwrite) NSString *name; | ||
+@property(readwrite) NSString *minimumVersion; | ||
+@property(readwrite) NSString *projectRoot; | ||
+@property(readwrite) NSString *company; | ||
+@property(readwrite) NSString *classPrefix; | ||
+@property(readwrite) NSString *developmentRegion; | ||
+@property(readwrite) NSArray *files; | ||
+ | ||
+- (BOOL)parse:(NSError**)pError; | ||
+ | ||
+- (id)initWithPath:(NSString*)path; | ||
+- (id)initWithName:(NSString*)name andDictionary:(NSDictionary*)dict; | ||
+@end | ||
+ | ||
+@implementation DDXcodeProjectFile | ||
+ | ||
+#pragma mark - | ||
+ | ||
+- (id)initWithPath:(NSString*)path { | ||
+ id pbxpath = nil; | ||
+ | ||
+ if([[path lastPathComponent] isEqualToString:@"project.pbxproj"]) { | ||
+ pbxpath = path; | ||
+ path = path.stringByDeletingLastPathComponent; | ||
+ } | ||
+ else { | ||
+ //path = path | ||
+ pbxpath = [path stringByAppendingPathComponent:@"project.pbxproj"]; | ||
+ } | ||
+ | ||
+ BOOL isDir = NO; | ||
+ NSAssert([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir], @"project doesnt exist"); | ||
+ NSAssert(isDir, @"project should be a directory"); | ||
+ NSAssert([[NSFileManager defaultManager] fileExistsAtPath:pbxpath isDirectory:nil], @"project has no pbx xml file"); | ||
+ | ||
+ if(self) { | ||
+ self.path = pbxpath; | ||
+ self.name = path.lastPathComponent.stringByDeletingPathExtension; | ||
+ } | ||
+ | ||
+ return self; | ||
+} | ||
+ | ||
+- (id)initWithName:(NSString*)name andDictionary:(NSDictionary*)dict { | ||
+ self = [super init]; | ||
+ | ||
+ if(self) { | ||
+ self.name = name; | ||
+ self.dictionary = dict; | ||
+ } | ||
+ | ||
+ return self; | ||
+} | ||
+ | ||
++ (id)xcodeProjectFileWithPath:(NSString*)path error:(NSError**)pError { | ||
+ id file = [[[self class] alloc] initWithPath:path]; | ||
+ if(file) { | ||
+ if([file parse:pError]) { | ||
+ return file; | ||
+ } | ||
+ } | ||
+ return nil; | ||
+} | ||
+ | ||
++ (id)xcodeProjectFileWithDictionary:(NSDictionary*)dict error:(NSError**)pError { | ||
+ id file = [[[self class] alloc] initWithDictionary:dict]; | ||
+ if(file) { | ||
+ if([file parse:pError]) { | ||
+ return file; | ||
+ } | ||
+ } | ||
+ return nil; | ||
+} | ||
+ | ||
+#pragma mark - main parse | ||
+ | ||
+- (BOOL)parse:(NSError**)pError { | ||
+ NSAssert(self.path.length || self.dictionary.count, @"we should have a non-empty path or non-empty dictionary"); | ||
+ | ||
+ //get initial dictionary | ||
+ if(self.path.length) { | ||
+ self.dictionary = [NSDictionary dictionaryWithContentsOfFile:self.path]; | ||
+ if(!self.dictionary) { | ||
+ *pError = [NSError errorWithCode:0 description:@"DDXcodeProjectFile can't be parsed." reason:@"cannot make plist from file contents"]; | ||
+ return NO; | ||
+ } | ||
+ } | ||
+ if(!self.dictionary) { | ||
+ *pError = [NSError errorWithCode:0 description:@"DDXcodeProjectFile can't be parsed." reason:@"cannot make plist from file contents"]; | ||
+ return NO; | ||
+ } | ||
+ | ||
+ //get main objects dictionary | ||
+ NSDictionary *objects = self.dictionary[@"objects"]; | ||
+ if(![objects isKindOfClass:[NSDictionary class]]) { | ||
+ *pError = [NSError errorWithCode:1 description:@"DDXcodeProjectFile can't be parsed." reason:@"cannot find main objects dictionary"]; | ||
+ return NO; | ||
+ } | ||
+ | ||
+ //set up _mutableFilesBuffer which will get filled in the parsing process.. or not :D | ||
+ _mutableFilesBuffer = [NSMutableArray arrayWithCapacity:objects.count/2]; | ||
+ | ||
+ //handle each object that can appear in a method found via string to SEL | ||
+ for (NSDictionary *object in objects.allValues) { | ||
+ if(![object isKindOfClass:[NSDictionary class]] || !object[@"isa"]) { | ||
+ *pError = [NSError errorWithCode:1 description:@"DDXcodeProjectFile can't be parsed." reason:[NSString stringWithFormat:@"cannot handle object %@", object]]; | ||
+ return NO; | ||
+ } | ||
+ | ||
+ //get method by isa | ||
+ SEL method = [self methodNameForIsa:object[@"isa"]]; | ||
+ if(!method) { | ||
+ *pError = [NSError errorWithCode:1 description:@"DDXcodeProjectFile can't be parsed." reason:[NSString stringWithFormat:@"cannot handle isa %@. Selector: %@", object[@"isa"], NSStringFromSelector(method)]]; | ||
+ return NO; | ||
+ } | ||
+ | ||
+ //if it doesnt respond to the method, log it and continue for now | ||
+ if(![self respondsToSelector:method]) { | ||
+ NSLog(@"Warn: dont handle %@", NSStringFromSelector(method)); | ||
+ continue; | ||
+ } | ||
+ | ||
+ //call the reflected method | ||
+#pragma clang diagnostic push | ||
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks" | ||
+ NSError *suberror = [self performSelector:method withObject:object]; | ||
+#pragma clang diagnostic pop | ||
+ | ||
+ //exit on error | ||
+ if(suberror) { | ||
+ *pError = suberror; | ||
+ return NO; | ||
+ } | ||
+ } | ||
+ | ||
+ //check mandatory... 1 file at least | ||
+ if( !_mutableFilesBuffer.count ) { | ||
+ *pError = [NSError errorWithCode:1 description:@"DDXcodeProjectFile can't be parsed." reason:@"cannot find manatory attributes in plist from file contents"]; | ||
+ return NO; | ||
+ } | ||
+ | ||
+ //resolve all | ||
+ NSError *suberror = [self resolveFiles:_mutableFilesBuffer]; | ||
+ if(suberror) { | ||
+ *pError = suberror; | ||
+ return NO; | ||
+ } | ||
+ | ||
+ //save buffer to property | ||
+ self.files = [NSArray arrayWithArray:_mutableFilesBuffer]; | ||
+ | ||
+ return YES; | ||
+} | ||
+ | ||
+- (SEL)methodNameForIsa:(NSString*)pbxIsa { | ||
+ NSString *name = [NSString stringWithFormat:@"parse%@:", pbxIsa]; | ||
+ return NSSelectorFromString(name); | ||
+} | ||
+ | ||
+- (NSError*)resolveFiles:(NSMutableArray*)files { | ||
+ | ||
+ return nil; | ||
+} | ||
+ | ||
+#pragma mark - parse individual objects | ||
+ | ||
+//- (NSError*)parsePBXBuildFile:(NSDictionary*)dict { | ||
+// return nil; | ||
+//} | ||
+//- (NSError*)parsePBXSourcesBuildPhase:(NSDictionary*)dict { | ||
+// return nil; | ||
+//} | ||
+ | ||
+// we only care about those for now | ||
+ | ||
+- (NSError*)parsePBXProject:(NSDictionary*)dict { | ||
+ if(!dict[@"attributes"]) { | ||
+ return [NSError errorWithCode:5 | ||
+ description:@"Can't parse project." | ||
+ reason:[NSString stringWithFormat:@"PBXProject without attributes: %@", dict]]; | ||
+ } | ||
+ | ||
+ self.classPrefix = dict[@"attributes"][@"CLASSPREFIX"]; | ||
+ self.company = dict[@"attributes"][@"ORGANIZATIONNAME"]; | ||
+ self.developmentRegion = dict[@"developmentRegion"]; | ||
+ self.minimumVersion = dict[@"compatibilityVersion"]; | ||
+ | ||
+ if(dict[@"projectRoot"]) { | ||
+ if(self.path.length) | ||
+ self.projectRoot = [self.path.stringByDeletingLastPathComponent.stringByDeletingLastPathComponent stringByAppendingPathComponent:dict[@"projectRoot"]]; | ||
+ else | ||
+ self.projectRoot = dict[@"projectRoot"]; | ||
+ } | ||
+ | ||
+ if(!self.classPrefix || !self.company || !self.developmentRegion || !self.minimumVersion || !self.projectRoot) { | ||
+ return [NSError errorWithCode:5 | ||
+ description:@"Can't parse project." | ||
+ reason:[NSString stringWithFormat:@"PBXProject missing mandatory attributes: %@", dict]]; | ||
+ } | ||
+ | ||
+ return nil; | ||
+} | ||
+ | ||
+- (NSError*)parsePBXFileReference:(NSDictionary*)dict { | ||
+ if(!dict[@"lastKnownFileType"] && dict[@"explicitFileType"]) { | ||
+ NSMutableDictionary *mdict = dict.mutableCopy; | ||
+ mdict[@"lastKnownFileType"] = dict[@"explicitFileType"]; | ||
+ dict = mdict; | ||
+ } | ||
+ | ||
+ if(!dict[@"path"] || !dict[@"lastKnownFileType"]) { | ||
+ return [NSError errorWithCode:5 | ||
+ description:@"Can't parse project." | ||
+ reason:[NSString stringWithFormat:@"PBXFileReference without path/type: %@", dict]]; | ||
+ } | ||
+ | ||
+ //add the path - TO BE resolved later :D | ||
+ [_mutableFilesBuffer addObject:@{@"path": dict[@"path"], @"type": dict[@"lastKnownFileType"]}]; | ||
+ | ||
+ return nil; //no error | ||
+} | ||
+ | ||
+@end |
0 comments on commit
6b8302c