Skip to content

Commit

Permalink
made it possible to just give the path to an xcodeproject to appledoc.
Browse files Browse the repository at this point in the history
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
Daij-Djan committed Sep 7, 2012
1 parent a630098 commit 6b8302c
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 1 deletion.
34 changes: 33 additions & 1 deletion Application/GBAppledocApplication.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import "GBGenerator.h"
#import "GBApplicationSettingsProvider.h"
#import "GBAppledocApplication.h"
#import "DDXcodeProjectFile.h"

static NSString *kGBArgInputPath = @"input";
static NSString *kGBArgOutputPath = @"output";
Expand Down Expand Up @@ -101,6 +102,7 @@ - (void)validateSettingsAndArguments:(NSArray *)arguments;
- (NSString *)standardizeCurrentDirectoryForPath:(NSString *)path;
- (NSString *)combineBasePath:(NSString *)base withRelativePath:(NSString *)path;

- (void)injectXcodeSettingsFromArguments:(NSArray *)arguments;
- (void)injectGlobalSettingsFromArguments:(NSArray *)arguments;
- (void)injectProjectSettingsFromArguments:(NSArray *)arguments;
- (void)overrideSettingsWithGlobalSettingsFromPath:(NSString *)path;
Expand Down Expand Up @@ -318,6 +320,7 @@ - (void)application:(DDCliApplication *)app willParseOptions:(DDGetoptLongParser
{ nil, 0, 0 },
};
NSArray *arguments = [[NSProcessInfo processInfo] arguments];
[self injectXcodeSettingsFromArguments:arguments];
[self injectGlobalSettingsFromArguments:arguments];
[self injectProjectSettingsFromArguments:arguments];
[optionParser addOptionsFromTable:options];
Expand Down Expand Up @@ -455,7 +458,36 @@ - (NSString *)combineBasePath:(NSString *)base withRelativePath:(NSString *)path
return [base stringByAppendingPathComponent:path];
}

#pragma mark Global and project settings handling
#pragma mark Xcode, Global and project settings handling

- (void)injectXcodeSettingsFromArguments:(NSArray *)arguments {
//check if even deal with a project
NSString *path = [arguments objectAtIndex:1];
if(![path.pathExtension isEqualToString:@"xcodeproj"])
return;

//parse the file and get a representation of it
NSError *error = nil;
DDXcodeProjectFile *file = [DDXcodeProjectFile xcodeProjectFileWithPath:path error:&error];
if(!file) {
NSLog(@"Failed to parse pbx at %@: %@", path, error);
return;
}

//set basic vars
[self setProjectName:file.name];
[self setProjectCompany:file.company];

//prepare docset
[self setCreateDocset:YES];
[self setInstallDocset:YES];
[self setDocsetBundleName:file.name];
[self setCompanyId:[file.company stringByAppendingFormat:@".%@", file.name].lowercaseString];

//set output path to be next to project
[self.additionalInputPaths addObject:file.projectRoot];
[self setOutput:file.projectRoot];
}

- (void)injectGlobalSettingsFromArguments:(NSArray *)arguments {
// This is where we override factory defaults (factory defaults with global templates). This needs to be sent before giving DDCli a chance to go through parameters! DDCli will "take care" (or more correct: it's KVC messages will) of overriding with command line arguments. Note that we scan the arguments backwards to get the latest template value - this is what we'll get with DDCli later on anyway. If no template path is given, check predefined paths.
Expand Down
39 changes: 39 additions & 0 deletions Common/DDXcodeProjectFile.h
Original file line number Diff line number Diff line change
@@ -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
240 changes: 240 additions & 0 deletions Common/DDXcodeProjectFile.m
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions appledoc.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@
73FC741D11FE215E00AAD0B9 /* GBMethodData.m in Sources */ = {isa = PBXBuildFile; fileRef = 73FC741C11FE215E00AAD0B9 /* GBMethodData.m */; };
73FC742D11FE274300AAD0B9 /* GBMethodArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 73FC742C11FE274300AAD0B9 /* GBMethodArgument.m */; };
8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; };
B498BCDF15F6250500183EB5 /* DDXcodeProjectFile.m in Sources */ = {isa = PBXBuildFile; fileRef = B498BCDE15F6250500183EB5 /* DDXcodeProjectFile.m */; };
B498BCE015F6250500183EB5 /* DDXcodeProjectFile.m in Sources */ = {isa = PBXBuildFile; fileRef = B498BCDE15F6250500183EB5 /* DDXcodeProjectFile.m */; };
D818BAC0152039DC00B26451 /* GHUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 733EA0F1122BDC5B0060CBDE /* GHUnit.framework */; };
D818BACE15203AA700B26451 /* GHUnit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 733EA0F1122BDC5B0060CBDE /* GHUnit.framework */; };
D818BACF15203AA700B26451 /* OCMock.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 73FC6EDF11FCE01E00AAD0B9 /* OCMock.framework */; };
Expand Down Expand Up @@ -508,6 +510,8 @@
73FC742C11FE274300AAD0B9 /* GBMethodArgument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GBMethodArgument.m; sourceTree = "<group>"; };
73FC743511FE2CEB00AAD0B9 /* GBMethodDataTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GBMethodDataTesting.m; sourceTree = "<group>"; };
8DD76FA10486AA7600D96B5E /* appledoc */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = appledoc; sourceTree = BUILT_PRODUCTS_DIR; };
B498BCDD15F6250500183EB5 /* DDXcodeProjectFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDXcodeProjectFile.h; sourceTree = "<group>"; };
B498BCDE15F6250500183EB5 /* DDXcodeProjectFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDXcodeProjectFile.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -759,6 +763,8 @@
73D54C9411F8D1CC00CCDDB0 /* Common */ = {
isa = PBXGroup;
children = (
B498BCDD15F6250500183EB5 /* DDXcodeProjectFile.h */,
B498BCDE15F6250500183EB5 /* DDXcodeProjectFile.m */,
73D54CA711F8D27F00CCDDB0 /* GBLog.h */,
73D54CA811F8D27F00CCDDB0 /* GBLog.m */,
73397A2612A5070700EDC035 /* GBTask.h */,
Expand Down Expand Up @@ -1271,6 +1277,7 @@
73E1B7D4130BE72100E3D710 /* GBCommentsProcessor-RegistrationsTesting.m in Sources */,
73E1B7D6130BE7C000E3D710 /* GBCommentsProcessor-PreprocessingTesting.m in Sources */,
73E0CF46131047E700FAFEC0 /* GBCommentsProcessor-MarkdownTesting.m in Sources */,
B498BCE015F6250500183EB5 /* DDXcodeProjectFile.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1353,6 +1360,7 @@
73AF4ECC130938F3001152DB /* GBCommentComponentsList.m in Sources */,
73FAC0F9130BCB6F00F22475 /* GBCommentArgument.m in Sources */,
73C4C8611377DFF70024FA77 /* GBExitCodes.m in Sources */,
B498BCDF15F6250500183EB5 /* DDXcodeProjectFile.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down

0 comments on commit 6b8302c

Please sign in to comment.