Merge branch 'refs/heads/XcodeCapp3'
No files matched your search
@@ -11,5 +11,6 @@ xcuserdata/
|
||||
!*.xcodeproj/project.pbxproj
|
||||
*.xCodeSupport/
|
||||
*.XcodeSupport/
|
||||
*XcodeSupport/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
@@ -1,5 +0,0 @@
|
||||
.DS_Store
|
||||
*.mode1v3
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
build
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2011 Antoine Mercadal (<primalmotion@archipelproject.org>)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <CoreServices/CoreServices.h>
|
||||
#import "PRHEmptyGrowlDelegate.h"
|
||||
#import "TNXCodeCapp.h"
|
||||
#import "TNErrorDataView.h"
|
||||
|
||||
@interface AppController : NSObject <NSMenuDelegate, NSTableViewDataSource, NSTableViewDelegate>
|
||||
{
|
||||
IBOutlet NSMenu *statusMenu;
|
||||
IBOutlet NSMenuItem *menuItemOpenXCode;
|
||||
IBOutlet NSMenuItem *menuItemStartStop;
|
||||
IBOutlet NSPanel *errorsPanel;
|
||||
IBOutlet NSTableView *errorsTable;
|
||||
IBOutlet NSPanel *aboutWindow;
|
||||
IBOutlet NSWindow *helpWindow;
|
||||
IBOutlet NSTextView *helpTextView;
|
||||
IBOutlet NSTextField *labelVersion;
|
||||
IBOutlet NSPopUpButton *buttonPreferencesAPIMode;
|
||||
IBOutlet NSButton *checkBoxPreferencesReactMode;
|
||||
IBOutlet NSUserDefaultsController *preferencesController;
|
||||
IBOutlet TNXCodeCapp *__strong xcc;
|
||||
IBOutlet NSWindow *windowDebug;
|
||||
IBOutlet NSMenuItem *menuDebug;
|
||||
IBOutlet NSMenuItem *menuHistory;
|
||||
IBOutlet TNErrorDataView *dataViewError;
|
||||
|
||||
NSImage *_iconActive;
|
||||
NSImage *_iconInactive;
|
||||
NSImage *_iconWorking;
|
||||
NSStatusItem *_statusItem;
|
||||
PRHEmptyGrowlDelegate *growlDelegateRef;
|
||||
NSData *_archivedDataView;
|
||||
}
|
||||
|
||||
@property BOOL supportsFileModeListening;
|
||||
@property (strong) TNXCodeCapp *xcc;
|
||||
|
||||
+ (AppController *)sharedAppController;
|
||||
|
||||
- (BOOL)validateMenuItem:(NSMenuItem*)menuItem;
|
||||
- (void)registerDefaults;
|
||||
- (void)growlWithTitle:(NSString *)aTitle message:(NSString *)aMessage;
|
||||
- (void)openCenteredWindow:(NSWindow *)aWindow;
|
||||
- (void)_prepareHistoryMenu;
|
||||
|
||||
- (IBAction)chooseFolder:(id)aSender;
|
||||
- (IBAction)openErrors:(id)sender;
|
||||
- (IBAction)clearErrors:(id)sender;
|
||||
- (IBAction)openXCode:(id)aSender;
|
||||
- (IBAction)stopListener:(id)aSender;
|
||||
- (IBAction)openHelp:(id)aSender;
|
||||
- (IBAction)openAbout:(id)aSender;
|
||||
- (IBAction)updatePreferences:(id)aSender;
|
||||
- (IBAction)switchProject:(id)aSender;
|
||||
- (IBAction)clearProjectHistory:(id)aSender;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,443 +0,0 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2011 Antoine Mercadal (<primalmotion@archipelproject.org>)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#import <AppKit/NSApplication.h>
|
||||
|
||||
#import "AppController.h"
|
||||
#include "macros.h"
|
||||
|
||||
|
||||
AppController *SharedAppControllerInstance = nil;
|
||||
|
||||
float heightForStringDrawing(NSString *myString, NSFont *myFont, float myWidth)
|
||||
{
|
||||
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:myString];
|
||||
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(myWidth, FLT_MAX)];
|
||||
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
|
||||
|
||||
[layoutManager addTextContainer:textContainer];
|
||||
[textStorage addLayoutManager:layoutManager];
|
||||
[textStorage addAttribute:NSFontAttributeName value:myFont range:NSMakeRange(0, [textStorage length])];
|
||||
[textContainer setLineFragmentPadding:0.0];
|
||||
|
||||
(void) [layoutManager glyphRangeForTextContainer:textContainer];
|
||||
return [layoutManager usedRectForTextContainer:textContainer].size.height;
|
||||
}
|
||||
|
||||
@implementation AppController
|
||||
|
||||
@synthesize supportsFileModeListening;
|
||||
@synthesize xcc;
|
||||
|
||||
+ (AppController *)sharedAppController
|
||||
{
|
||||
return SharedAppControllerInstance;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization
|
||||
|
||||
/*!
|
||||
Called when NIB is ready
|
||||
*/
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
SharedAppControllerInstance = self;
|
||||
|
||||
_archivedDataView = [NSKeyedArchiver archivedDataWithRootObject:dataViewError];
|
||||
|
||||
if (!growlDelegateRef)
|
||||
growlDelegateRef = [[PRHEmptyGrowlDelegate alloc] init];
|
||||
|
||||
[GrowlApplicationBridge setGrowlDelegate:growlDelegateRef];
|
||||
|
||||
NSBundle *bundle = [NSBundle mainBundle];
|
||||
|
||||
[labelVersion setStringValue:[NSString stringWithFormat:@"Version %@", [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]]];
|
||||
|
||||
[self registerDefaults];
|
||||
|
||||
_iconInactive = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:@"xcodecapp-icon-inactive" ofType:@"png"]];
|
||||
_iconActive = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:@"xcodecapp-icon-active" ofType:@"png"]];
|
||||
_iconWorking = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:@"xcodecapp-icon-working" ofType:@"png"]];
|
||||
[_iconActive setSize:NSMakeSize(14.0, 16.0)];
|
||||
[_iconInactive setSize:NSMakeSize(14.0, 16.0)];
|
||||
[_iconWorking setSize:NSMakeSize(14.0, 16.0)];
|
||||
|
||||
_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
|
||||
[_statusItem setMenu:statusMenu];
|
||||
[_statusItem setImage:_iconInactive];
|
||||
[_statusItem setHighlightMode:YES];
|
||||
[statusMenu setDelegate:self];
|
||||
|
||||
if ([[NSUserDefaults standardUserDefaults] integerForKey:@"FirstLaunch"])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:0] forKey:@"FirstLaunch"];
|
||||
[self openHelp:self];
|
||||
}
|
||||
|
||||
[xcc setDelegate:self];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(XCodeCappConversionDidStart:) name:XCCConversionStartNotification object:xcc];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(XCodeCappConversionDidStop:) name:XCCConversionStopNotification object:xcc];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(XCodeCappPopulateProject:) name:XCCDidPopulateProjectNotification object:xcc];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(XCodeCappListeningDidStart:) name:XCCListeningStartNotification object:xcc];
|
||||
|
||||
[helpTextView setTextContainerInset:NSSizeFromCGSize(CGSizeMake(10.0, 10.0))];
|
||||
|
||||
[xcc start];
|
||||
|
||||
[self _prepareHistoryMenu];
|
||||
}
|
||||
|
||||
/*!
|
||||
Checks if aplication should show the debug window
|
||||
*/
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notif
|
||||
{
|
||||
CGEventRef event = CGEventCreate(NULL);
|
||||
CGEventFlags modifiers = CGEventGetFlags(event);
|
||||
CFRelease(event);
|
||||
|
||||
if (modifiers & kCGEventFlagMaskAlternate)
|
||||
{
|
||||
[statusMenu insertItem:menuDebug atIndex:6];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Register the application defaults
|
||||
*/
|
||||
- (void)registerDefaults
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSNumber *streamEventIdSinceNow = [NSNumber numberWithUnsignedLongLong:kFSEventStreamEventIdSinceNow];
|
||||
NSMutableDictionary *appDefaults = [NSMutableDictionary new];
|
||||
|
||||
[appDefaults setObject:streamEventIdSinceNow forKey:@"lastEventId"];
|
||||
[appDefaults setObject:[NSNumber numberWithInt:1] forKey:@"FirstLaunch"];
|
||||
[appDefaults setObject:[NSNumber numberWithInt:0] forKey:@"XCCAPIMode"];
|
||||
[appDefaults setObject:[NSNumber numberWithInt:1] forKey:@"XCCReactMode"];
|
||||
[appDefaults setObject:[NSNumber numberWithInt:1] forKey:@"XCCReopenLastProject"];
|
||||
[appDefaults setObject:[[NSArray alloc] init] forKey:@"XCCProjectHistory"];
|
||||
|
||||
[defaults registerDefaults:appDefaults];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Notification handlers
|
||||
|
||||
/*!
|
||||
Handle cleaning operation when application will stop.
|
||||
It will stop the FSEvent listener, and store the last event id
|
||||
*/
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app
|
||||
{
|
||||
[xcc clear];
|
||||
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when XCC start a conversion
|
||||
@param aNotification the notification
|
||||
*/
|
||||
- (void)XCodeCappConversionDidStart:(NSNotification *)aNotification
|
||||
{
|
||||
[_statusItem setImage:_iconWorking];
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when XCC finish a conversion
|
||||
@param aNotification the notification
|
||||
*/
|
||||
- (void)XCodeCappConversionDidStop:(NSNotification *)aNotification
|
||||
{
|
||||
[_statusItem setImage:_iconActive];
|
||||
|
||||
if ([errorsPanel isVisible])
|
||||
[errorsTable reloadData];
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when XCC has populated a project
|
||||
@param aNotification the notification
|
||||
*/
|
||||
- (void)XCodeCappPopulateProject:(NSNotification *)aNotification
|
||||
{
|
||||
[self growlWithTitle:@"Project loaded" message:[[[aNotification userInfo] objectForKey:@"URL"] path]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when XCC start a to listen to a project
|
||||
@param aNotification the notification
|
||||
*/
|
||||
- (void)XCodeCappListeningDidStart:(NSNotification *)aNotification
|
||||
{
|
||||
[_statusItem setImage:_iconActive];
|
||||
[menuItemStartStop setTitle:[NSString stringWithFormat:@"Stop Listening to “%@”", [xcc currentProjectName]]];
|
||||
[menuItemStartStop setAction:@selector(stopListener:)];
|
||||
|
||||
[self growlWithTitle:@"Listening to project" message:[[xcc currentProjectURL] path]];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Utilities
|
||||
|
||||
/*!
|
||||
Simple growl wrapper
|
||||
@param aTitle the growl title
|
||||
@param aMessage the growl message
|
||||
*/
|
||||
- (void)growlWithTitle:(NSString *)aTitle message:(NSString *)aMessage
|
||||
{
|
||||
[GrowlApplicationBridge notifyWithTitle:aTitle
|
||||
description:aMessage
|
||||
notificationName:@"DefaultNotifications"
|
||||
iconData:nil
|
||||
priority:0
|
||||
isSticky:NO
|
||||
clickContext:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Prepare the history menu
|
||||
*/
|
||||
- (void)_prepareHistoryMenu
|
||||
{
|
||||
NSMenu *menu = [[NSMenu alloc] init];
|
||||
NSArray *projectHistory = [[NSUserDefaults standardUserDefaults] objectForKey:@"XCCProjectHistory"];
|
||||
|
||||
for(int i = 0; i < [projectHistory count]; i++)
|
||||
{
|
||||
NSString *itemTitle = [[projectHistory objectAtIndex:i] lastPathComponent];
|
||||
NSString *projectPath = [[projectHistory objectAtIndex:i] stringByStandardizingPath];
|
||||
NSString *currentProjectPath = [[[xcc currentProjectURL] path] stringByStandardizingPath];
|
||||
NSMenuItem *item = [menu addItemWithTitle:itemTitle action:@selector(switchProject:) keyEquivalent:@""];
|
||||
|
||||
[item setRepresentedObject:projectPath];
|
||||
|
||||
if ([currentProjectPath isEqualToString:projectPath])
|
||||
[item setAction:nil];
|
||||
}
|
||||
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
[menu addItemWithTitle:@"Clear history" action:@selector(clearProjectHistory:) keyEquivalent:@""];
|
||||
|
||||
[menuHistory setEnabled:([projectHistory count]) ? YES : NO];
|
||||
[menuHistory setSubmenu:menu];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Actions
|
||||
|
||||
/*!
|
||||
Save preferences
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)updatePreferences:(id)aSender
|
||||
{
|
||||
[preferencesController save:aSender];
|
||||
NSLog(@"Preferences change notified");
|
||||
|
||||
[xcc configure];
|
||||
}
|
||||
|
||||
/*!
|
||||
Open the folder chooser and eventually start to listen
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)chooseFolder:(id)aSender
|
||||
{
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
|
||||
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
||||
|
||||
[openPanel setCanChooseDirectories:YES];
|
||||
[openPanel setCanCreateDirectories:YES];
|
||||
[openPanel setTitle:@"Choose Cappuccino Project"];
|
||||
[openPanel setCanChooseFiles:NO];
|
||||
|
||||
if ([openPanel runModal] != NSFileHandlingPanelOKButton)
|
||||
return;
|
||||
|
||||
NSString *projectPath = [NSString stringWithFormat:@"%@/", [[[openPanel URLs] objectAtIndex:0] path]];
|
||||
NSMutableArray *projectHistory = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:@"XCCProjectHistory"]];
|
||||
|
||||
if ([projectHistory containsObject:[projectPath stringByStandardizingPath]])
|
||||
[projectHistory removeObject:[projectPath stringByStandardizingPath]];
|
||||
|
||||
[projectHistory insertObject:[projectPath stringByStandardizingPath] atIndex:0];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setObject:projectHistory forKey:@"XCCProjectHistory"];
|
||||
|
||||
[xcc listenProjectAtPath:projectPath];
|
||||
|
||||
[self _prepareHistoryMenu];
|
||||
}
|
||||
|
||||
/*!
|
||||
Stop listening to a project
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)stopListener:(id)aSender
|
||||
{
|
||||
[xcc clear];
|
||||
|
||||
[_statusItem setImage:_iconInactive];
|
||||
[menuItemStartStop setTitle:@"Listen to Project…"];
|
||||
[menuItemStartStop setAction:@selector(chooseFolder:)];
|
||||
[self _prepareHistoryMenu];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"LastOpenedPath"];
|
||||
}
|
||||
|
||||
- (IBAction)switchProject:(id)aSender
|
||||
{
|
||||
NSString *newPath = [aSender representedObject];
|
||||
|
||||
[self stopListener:aSender];
|
||||
[xcc listenProjectAtPath:newPath];
|
||||
[self _prepareHistoryMenu];
|
||||
}
|
||||
|
||||
- (IBAction)clearProjectHistory:(id)aSender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[NSArray array] forKey:@"XCCProjectHistory"];
|
||||
[self _prepareHistoryMenu];
|
||||
}
|
||||
|
||||
/*!
|
||||
Open the xCode support project in xCode
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)openXCode:(id)aSender
|
||||
{
|
||||
if (![xcc currentProjectURL])
|
||||
return;
|
||||
|
||||
DLog(@"Opening Xcode project at URL: '%@'", [[xcc XCodeSupportProject] path]);
|
||||
system([[NSString stringWithFormat:@"open \"%@\"", [[xcc XCodeSupportProject] path]] UTF8String]);
|
||||
}
|
||||
|
||||
/*!
|
||||
Open the errors window
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)openErrors:(id)aSender
|
||||
{
|
||||
[self openCenteredWindow:errorsPanel];
|
||||
}
|
||||
|
||||
/*!
|
||||
Clear all errors in errors table
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)clearErrors:(id)sender
|
||||
{
|
||||
[[xcc errorList] removeAllObjects];
|
||||
[errorsTable reloadData];
|
||||
}
|
||||
|
||||
/*!
|
||||
Open the help file
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)openHelp:(id)aSender
|
||||
{
|
||||
[helpTextView readRTFDFromFile:[[NSBundle mainBundle] pathForResource:@"help" ofType:@"rtfd"]];
|
||||
|
||||
[self openCenteredWindow:helpWindow];
|
||||
}
|
||||
|
||||
/*!
|
||||
Open the about window
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)openAbout:(id)aSender
|
||||
{
|
||||
[self openCenteredWindow:aboutWindow];
|
||||
}
|
||||
|
||||
/*!
|
||||
Open the preferences window
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)openPreferences:(id)aSender
|
||||
{
|
||||
[self openCenteredWindow:windowDebug];
|
||||
}
|
||||
|
||||
/*!
|
||||
Open a centered window.
|
||||
*/
|
||||
- (void)openCenteredWindow:(NSWindow *)aWindow
|
||||
{
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
|
||||
[aWindow center];
|
||||
[aWindow makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Delegates
|
||||
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)aMenuItem
|
||||
{
|
||||
if (aMenuItem == menuItemOpenXCode)
|
||||
return !![xcc currentProjectURL];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
|
||||
{
|
||||
return [[xcc errorList] count];
|
||||
}
|
||||
|
||||
- (id)tableView:(NSTableView*)aTableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
return [[xcc errorList] objectAtIndex:row];
|
||||
}
|
||||
|
||||
- (void)tableViewColumnDidResize:(NSNotification *)tableView
|
||||
{
|
||||
[errorsTable noteHeightOfRowsWithIndexesChanged:
|
||||
[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [[xcc errorList] count])]];
|
||||
}
|
||||
|
||||
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
return [NSKeyedUnarchiver unarchiveObjectWithData:_archivedDataView];
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)aRow
|
||||
{
|
||||
NSString *content = [[[xcc errorList] objectAtIndex:aRow] objectForKey:@"message"];
|
||||
NSFont *currentFont = [[dataViewError fieldMessage] font];
|
||||
float height = heightForStringDrawing(content, currentFont, [tableView frame].size.width);
|
||||
|
||||
return height + 31;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* This file is a part of program xcodecapp-cocoa
|
||||
* Copyright (C) 2011 Antoine Mercadal (primalmotion@archipelproject.org)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import "FSEventCallback.h"
|
||||
|
||||
/*!
|
||||
This is the FSEvent callback
|
||||
*/
|
||||
void fsevents_callback(ConstFSEventStreamRef streamRef,
|
||||
void *userData,
|
||||
size_t numEvents,
|
||||
void *eventPaths,
|
||||
const FSEventStreamEventFlags eventFlags[],
|
||||
const FSEventStreamEventId eventIds[])
|
||||
{
|
||||
TNXCodeCapp *xcc = (TNXCodeCapp *)userData;
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < numEvents; i++)
|
||||
{
|
||||
NSString *path = [(NSArray *)eventPaths objectAtIndex:i];
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
|
||||
// kFSEventStreamEventFlagItemIsFile = 0x00010000
|
||||
if (!(eventFlags[i] & 0x00010000)
|
||||
|| [xcc isPathMatchingIgnoredPaths:path]
|
||||
|| (![xcc isXIBFile:path] && ![xcc isObjJFile:path] && ![xcc isXCCIgnoreFile:path]))
|
||||
continue;
|
||||
|
||||
// kFSEventStreamEventFlagItemRemoved = 0x00000200
|
||||
if (eventFlags[i] & 0x00000200)
|
||||
{
|
||||
[xcc handleFileRemoval:path];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"this file has been modified or created");
|
||||
[xcc handleFileModification:path notify:YES];
|
||||
}
|
||||
#else
|
||||
NSArray *subpaths = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
|
||||
for(NSString *currentPath in subpaths)
|
||||
[xcc handleFileModification:currentPath notify:YES];
|
||||
#endif
|
||||
|
||||
[xcc updateLastEventId:eventIds[i]];
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2011 Antoine Mercadal (<primalmotion@archipelproject.org>)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef xcodecapp_cocoa_FSEventCallback_h
|
||||
#define xcodecapp_cocoa_FSEventCallback_h
|
||||
|
||||
#import "TNXCodeCapp.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_6
|
||||
# define kFSEventStreamCreateFlagFileEvents 0x00000010
|
||||
# define kFSEventStreamEventFlagItemIsFile 0x00010000
|
||||
# define kFSEventStreamEventFlagItemRemoved 0x00000200
|
||||
# define kFSEventStreamEventFlagItemCreated 0x00000200
|
||||
# define kFSEventStreamEventFlagItemModified 0x00001000
|
||||
# define kFSEventStreamEventFlagItemInodeMetaMod 0x00000400
|
||||
# define kFSEventStreamEventFlagItemRenamed 0x00000800
|
||||
# define kFSEventStreamEventFlagItemFinderInfoMod 0x00002000
|
||||
# define kFSEventStreamEventFlagItemChangeOwner 0x00004000
|
||||
# define kFSEventStreamEventFlagItemXattrMod 0x00008000
|
||||
#endif
|
||||
|
||||
void fsevents_callback(ConstFSEventStreamRef, void*, size_t, void*, const FSEventStreamEventFlags*, const FSEventStreamEventId*);
|
||||
|
||||
#endif
|
||||
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2011 Antoine Mercadal (<primalmotion@archipelproject.org>)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import "AppController.h"
|
||||
#import "FSEventCallback.h"
|
||||
#import "macros.h"
|
||||
|
||||
|
||||
/*!
|
||||
This is the FSEvent callback for 10.7
|
||||
*/
|
||||
void fsevents_callback(ConstFSEventStreamRef streamRef,
|
||||
void *userData,
|
||||
size_t numEvents,
|
||||
void *eventPaths,
|
||||
const FSEventStreamEventFlags eventFlags[],
|
||||
const FSEventStreamEventId eventIds[])
|
||||
{
|
||||
TNXCodeCapp *xcc = (__bridge TNXCodeCapp *)userData;
|
||||
BOOL useFileBasedListening = [xcc supportsFileBasedListening];
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < numEvents; i++)
|
||||
{
|
||||
[xcc updateLastEventId:eventIds[i]];
|
||||
|
||||
FSEventStreamEventFlags flags = eventFlags[i];
|
||||
|
||||
NSString *path = [[(__bridge NSArray *)eventPaths objectAtIndex:i] stringByStandardizingPath];
|
||||
|
||||
if (useFileBasedListening)
|
||||
{
|
||||
BOOL conditionIsFile = flags & kFSEventStreamEventFlagItemIsFile;
|
||||
BOOL conditionIsDirectory = NO;
|
||||
BOOL conditionIsIgnored = [xcc isPathMatchingIgnoredPaths:path];
|
||||
BOOL conditionIsValidFile = [xcc isXIBFile:path] || [xcc isObjJFile:path] || [xcc isXCCIgnoreFile:path];
|
||||
BOOL conditionPathExists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&conditionIsDirectory];
|
||||
|
||||
if (conditionIsIgnored)
|
||||
continue;
|
||||
|
||||
if (conditionIsFile && !conditionIsValidFile)
|
||||
continue;
|
||||
|
||||
// Events are not so reliable. For example, moving a folder to the trash is not
|
||||
// a deletion. In order to simplify the code, we simply tidyUp the project when we receive
|
||||
// an event.
|
||||
[xcc tidyShadowedFiles];
|
||||
|
||||
if (conditionIsDirectory)
|
||||
continue;
|
||||
|
||||
if (!conditionPathExists)
|
||||
{
|
||||
DLog(@"File removed: %@", path);
|
||||
[xcc handleFileRemoval:path];
|
||||
}
|
||||
else
|
||||
{
|
||||
DLog(@"File modified/added: %@", path);
|
||||
[xcc handleFileModification:path notify:YES];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We should drop support for Snow Leopard soon.
|
||||
|
||||
BOOL isDir = NO;
|
||||
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
|
||||
|
||||
// If for some reasons the path is not a directory,
|
||||
// we don't want to deal with it in this mode.
|
||||
if (!isDir)
|
||||
continue;
|
||||
|
||||
[xcc tidyShadowedFiles];
|
||||
|
||||
NSArray *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
|
||||
|
||||
for (NSString *subpath in subpaths)
|
||||
{
|
||||
NSString *fullPath = [[NSString stringWithFormat:@"%@/%@", path, subpath] stringByStandardizingPath];
|
||||
|
||||
if ([xcc isPathMatchingIgnoredPaths:fullPath]
|
||||
|| (![xcc isXIBFile:fullPath] && ![xcc isObjJFile:fullPath] && ![xcc isXCCIgnoreFile:fullPath]))
|
||||
continue;
|
||||
|
||||
NSDate *lastModifiedDate = [xcc lastModificationDateForPath:fullPath];
|
||||
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:nil];
|
||||
NSDate *fileModDate = [fileAttributes objectForKey:NSFileModificationDate];
|
||||
|
||||
if ([fileModDate compare:lastModifiedDate] == NSOrderedDescending)
|
||||
{
|
||||
[xcc updateLastModificationDate:fileModDate forPath:fullPath];
|
||||
[xcc handleFileModification:fullPath notify:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[xcc updateUserDefaultsWithLastEventId];
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#include "GrowlDefines.h"
|
||||
|
||||
#ifdef __OBJC__
|
||||
# include "GrowlApplicationBridge.h"
|
||||
#endif
|
||||
#include "GrowlApplicationBridge-Carbon.h"
|
||||
@@ -1,780 +0,0 @@
|
||||
//
|
||||
// GrowlApplicationBridge-Carbon.h
|
||||
// Growl
|
||||
//
|
||||
// Created by Mac-arena the Bored Zo on Wed Jun 18 2004.
|
||||
// Based on GrowlApplicationBridge.h by Evan Schoenberg.
|
||||
// This source code is in the public domain. You may freely link it into any
|
||||
// program.
|
||||
//
|
||||
|
||||
#ifndef _GROWLAPPLICATIONBRIDGE_CARBON_H_
|
||||
#define _GROWLAPPLICATIONBRIDGE_CARBON_H_
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <Carbon/Carbon.h>
|
||||
|
||||
#ifndef GROWL_EXPORT
|
||||
#define GROWL_EXPORT __attribute__((visibility("default"))) DEPRECATED_ATTRIBUTE
|
||||
#endif
|
||||
|
||||
/*! @header GrowlApplicationBridge-Carbon.h
|
||||
* @abstract Declares an API that Carbon applications can use to interact with Growl.
|
||||
* @discussion GrowlApplicationBridge uses a delegate to provide information //XXX
|
||||
* to Growl (such as your application's name and what notifications it may
|
||||
* post) and to provide information to your application (such as that Growl
|
||||
* is listening for notifications or that a notification has been clicked).
|
||||
*
|
||||
* You can set the Growldelegate with Growl_SetDelegate and find out the
|
||||
* current delegate with Growl_GetDelegate. See struct Growl_Delegate for more
|
||||
* information about the delegate.
|
||||
*/
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
/*! @struct Growl_Delegate
|
||||
* @abstract Delegate to supply GrowlApplicationBridge with information and respond to events.
|
||||
* @discussion The Growl delegate provides your interface to
|
||||
* GrowlApplicationBridge. When GrowlApplicationBridge needs information about
|
||||
* your application, it looks for it in the delegate; when Growl or the user
|
||||
* does something that you might be interested in, GrowlApplicationBridge
|
||||
* looks for a callback in the delegate and calls it if present
|
||||
* (meaning, if it is not <code>NULL</code>).
|
||||
* XXX on all of that
|
||||
* @field size The size of the delegate structure.
|
||||
* @field applicationName The name of your application.
|
||||
* @field registrationDictionary A dictionary describing your application and the notifications it can send out.
|
||||
* @field applicationIconData Your application's icon.
|
||||
* @field growlInstallationWindowTitle The title of the installation window.
|
||||
* @field growlInstallationInformation Text to display in the installation window.
|
||||
* @field growlUpdateWindowTitle The title of the update window.
|
||||
* @field growlUpdateInformation Text to display in the update window.
|
||||
* @field referenceCount A count of owners of the delegate.
|
||||
* @field retain Called when GrowlApplicationBridge receives this delegate.
|
||||
* @field release Called when GrowlApplicationBridge no longer needs this delegate.
|
||||
* @field growlIsReady Called when GrowlHelperApp is listening for notifications.
|
||||
* @field growlNotificationWasClicked Called when a Growl notification is clicked.
|
||||
* @field growlNotificationTimedOut Called when a Growl notification timed out.
|
||||
*/
|
||||
struct Growl_Delegate {
|
||||
/* @discussion This should be sizeof(struct Growl_Delegate).
|
||||
*/
|
||||
size_t size;
|
||||
|
||||
/*All of these attributes are optional.
|
||||
*Optional attributes can be NULL; required attributes that
|
||||
* are NULL cause setting the Growl delegate to fail.
|
||||
*XXX - move optional/required status into the discussion for each field
|
||||
*/
|
||||
|
||||
/* This name is used both internally and in the Growl preferences.
|
||||
*
|
||||
* This should remain stable between different versions and incarnations of
|
||||
* your application.
|
||||
* For example, "SurfWriter" is a good app name, whereas "SurfWriter 2.0" and
|
||||
* "SurfWriter Lite" are not.
|
||||
*
|
||||
* This can be <code>NULL</code> if it is provided elsewhere, namely in an
|
||||
* auto-discoverable plist file in your app bundle
|
||||
* (XXX refer to more information on that) or in registrationDictionary.
|
||||
*/
|
||||
CFStringRef applicationName;
|
||||
|
||||
/*
|
||||
* Must contain at least these keys:
|
||||
* GROWL_NOTIFICATIONS_ALL (CFArray):
|
||||
* Contains the names of all notifications your application may post.
|
||||
*
|
||||
* Can also contain these keys:
|
||||
* GROWL_NOTIFICATIONS_DEFAULT (CFArray):
|
||||
* Names of notifications that should be enabled by default.
|
||||
* If omitted, GROWL_NOTIFICATIONS_ALL will be used.
|
||||
* GROWL_APP_NAME (CFString):
|
||||
* Same as the applicationName member of this structure.
|
||||
* If both are present, the applicationName member shall prevail.
|
||||
* If this key is present, you may omit applicationName (set it to <code>NULL</code>).
|
||||
* GROWL_APP_ICON (CFData):
|
||||
* Same as the iconData member of this structure.
|
||||
* If both are present, the iconData member shall prevail.
|
||||
* If this key is present, you may omit iconData (set it to <code>NULL</code>).
|
||||
*
|
||||
* If you change the contents of this dictionary after setting the delegate,
|
||||
* be sure to call Growl_Reregister.
|
||||
*
|
||||
* This can be <code>NULL</code> if you have an auto-discoverable plist file in your app
|
||||
* bundle. (XXX refer to more information on that)
|
||||
*/
|
||||
CFDictionaryRef registrationDictionary;
|
||||
|
||||
/* The data can be in any format supported by NSImage. As of
|
||||
* Mac OS X 10.3, this includes the .icns, TIFF, JPEG, GIF, PNG, PDF, and
|
||||
* PICT formats.
|
||||
*
|
||||
* If this is not supplied, Growl will look up your application's icon by
|
||||
* its application name.
|
||||
*/
|
||||
CFDataRef applicationIconData;
|
||||
|
||||
/* Installer display attributes
|
||||
*
|
||||
* These four attributes are used by the Growl installer, if this framework
|
||||
* supports it.
|
||||
* For any of these being <code>NULL</code>, a localised default will be
|
||||
* supplied.
|
||||
*/
|
||||
|
||||
/* If this is <code>NULL</code>, Growl will use a default,
|
||||
* localized title.
|
||||
*
|
||||
* Only used if you're using Growl-WithInstaller.framework. Otherwise,
|
||||
* this member is ignored.
|
||||
*/
|
||||
CFStringRef growlInstallationWindowTitle;
|
||||
/* This information may be as long or short as desired (the
|
||||
* window will be sized to fit it). If Growl is not installed, it will
|
||||
* be displayed to the user as an explanation of what Growl is and what
|
||||
* it can do in your application.
|
||||
* It should probably note that no download is required to install.
|
||||
*
|
||||
* If this is <code>NULL</code>, Growl will use a default, localized
|
||||
* explanation.
|
||||
*
|
||||
* Only used if you're using Growl-WithInstaller.framework. Otherwise,
|
||||
* this member is ignored.
|
||||
*/
|
||||
CFStringRef growlInstallationInformation;
|
||||
/* If this is <code>NULL</code>, Growl will use a default,
|
||||
* localized title.
|
||||
*
|
||||
* Only used if you're using Growl-WithInstaller.framework. Otherwise,
|
||||
* this member is ignored.
|
||||
*/
|
||||
CFStringRef growlUpdateWindowTitle;
|
||||
/* This information may be as long or short as desired (the
|
||||
* window will be sized to fit it). If an older version of Growl is
|
||||
* installed, it will be displayed to the user as an explanation that an
|
||||
* updated version of Growl is included in your application and
|
||||
* no download is required.
|
||||
*
|
||||
* If this is <code>NULL</code>, Growl will use a default, localized
|
||||
* explanation.
|
||||
*
|
||||
* Only used if you're using Growl-WithInstaller.framework. Otherwise,
|
||||
* this member is ignored.
|
||||
*/
|
||||
CFStringRef growlUpdateInformation;
|
||||
|
||||
/* This member is provided for use by your retain and release
|
||||
* callbacks (see below).
|
||||
*
|
||||
* GrowlApplicationBridge never directly uses this member. Instead, it
|
||||
* calls your retain callback (if non-<code>NULL</code>) and your release
|
||||
* callback (if non-<code>NULL</code>).
|
||||
*/
|
||||
unsigned referenceCount;
|
||||
|
||||
//Functions. Currently all of these are optional (any of them can be NULL).
|
||||
|
||||
/* When you call Growl_SetDelegate(newDelegate), it will call
|
||||
* oldDelegate->release(oldDelegate), and then it will call
|
||||
* newDelegate->retain(newDelegate), and the return value from retain
|
||||
* is what will be set as the delegate.
|
||||
* (This means that this member works like CFRetain and -[NSObject retain].)
|
||||
* This member is optional (it can be <code>NULL</code>).
|
||||
* For a delegate allocated with malloc, this member would be
|
||||
* <code>NULL</code>.
|
||||
* @result A delegate to which GrowlApplicationBridge holds a reference.
|
||||
*/
|
||||
void *(*retain)(void *);
|
||||
/* When you call Growl_SetDelegate(newDelegate), it will call
|
||||
* oldDelegate->release(oldDelegate), and then it will call
|
||||
* newDelegate->retain(newDelegate), and the return value from retain
|
||||
* is what will be set as the delegate.
|
||||
* (This means that this member works like CFRelease and
|
||||
* -[NSObject release].)
|
||||
* This member is optional (it can be NULL).
|
||||
* For a delegate allocated with malloc, this member might be
|
||||
* <code>free</code>(3).
|
||||
*/
|
||||
void (*release)(void *);
|
||||
|
||||
/* Informs the delegate that Growl (specifically, the GrowlHelperApp) was
|
||||
* launched successfully (or was already running). The application can
|
||||
* take actions with the knowledge that Growl is installed and functional.
|
||||
*/
|
||||
void (*growlIsReady)(void);
|
||||
|
||||
/* Informs the delegate that a Growl notification was clicked. It is only
|
||||
* sent for notifications sent with a non-<code>NULL</code> clickContext,
|
||||
* so if you want to receive a message when a notification is clicked,
|
||||
* clickContext must not be <code>NULL</code> when calling
|
||||
* Growl_PostNotification or
|
||||
* Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext.
|
||||
*/
|
||||
void (*growlNotificationWasClicked)(CFPropertyListRef clickContext);
|
||||
|
||||
/* Informs the delegate that a Growl notification timed out. It is only
|
||||
* sent for notifications sent with a non-<code>NULL</code> clickContext,
|
||||
* so if you want to receive a message when a notification is clicked,
|
||||
* clickContext must not be <code>NULL</code> when calling
|
||||
* Growl_PostNotification or
|
||||
* Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext.
|
||||
*/
|
||||
void (*growlNotificationTimedOut)(CFPropertyListRef clickContext);
|
||||
};
|
||||
|
||||
/*! @struct Growl_Notification
|
||||
* @abstract Structure describing a Growl notification.
|
||||
* @discussion XXX
|
||||
* @field size The size of the notification structure.
|
||||
* @field name Identifies the notification.
|
||||
* @field title Short synopsis of the notification.
|
||||
* @field description Additional text.
|
||||
* @field iconData An icon for the notification.
|
||||
* @field priority An indicator of the notification's importance.
|
||||
* @field reserved Bits reserved for future usage.
|
||||
* @field isSticky Requests that a notification stay on-screen until dismissed explicitly.
|
||||
* @field clickContext An identifier to be passed to your click callback when a notification is clicked.
|
||||
* @field clickCallback A callback to call when the notification is clicked.
|
||||
*/
|
||||
struct Growl_Notification {
|
||||
/* This should be sizeof(struct Growl_Notification).
|
||||
*/
|
||||
size_t size;
|
||||
|
||||
/* The notification name distinguishes one type of
|
||||
* notification from another. The name should be human-readable, as it
|
||||
* will be displayed in the Growl preference pane.
|
||||
*
|
||||
* The name is used in the GROWL_NOTIFICATIONS_ALL and
|
||||
* GROWL_NOTIFICATIONS_DEFAULT arrays in the registration dictionary, and
|
||||
* in this member of the Growl_Notification structure.
|
||||
*/
|
||||
CFStringRef name;
|
||||
|
||||
/* A notification's title describes the notification briefly.
|
||||
* It should be easy to read quickly by the user.
|
||||
*/
|
||||
CFStringRef title;
|
||||
|
||||
/* The description supplements the title with more
|
||||
* information. It is usually longer and sometimes involves a list of
|
||||
* subjects. For example, for a 'Download complete' notification, the
|
||||
* description might have one filename per line. GrowlMail in Growl 0.6
|
||||
* uses a description of '%d new mail(s)' (formatted with the number of
|
||||
* messages).
|
||||
*/
|
||||
CFStringRef description;
|
||||
|
||||
/* The notification icon usually indicates either what
|
||||
* happened (it may have the same icon as e.g. a toolbar item that
|
||||
* started the process that led to the notification), or what it happened
|
||||
* to (e.g. a document icon).
|
||||
*
|
||||
* The icon data is optional, so it can be <code>NULL</code>. In that
|
||||
* case, the application icon is used alone. Not all displays support
|
||||
* icons.
|
||||
*
|
||||
* The data can be in any format supported by NSImage. As of Mac OS X
|
||||
* 10.3, this includes the .icns, TIFF, JPEG, GIF, PNG, PDF, and PICT form
|
||||
* ats.
|
||||
*/
|
||||
CFDataRef iconData;
|
||||
|
||||
/* Priority is new in Growl 0.6, and is represented as a
|
||||
* signed integer from -2 to +2. 0 is Normal priority, -2 is Very Low
|
||||
* priority, and +2 is Very High priority.
|
||||
*
|
||||
* Not all displays support priority. If you do not wish to assign a
|
||||
* priority to your notification, assign 0.
|
||||
*/
|
||||
signed int priority;
|
||||
|
||||
/* These bits are not used in Growl 0.6. Set them to 0.
|
||||
*/
|
||||
unsigned reserved: 31;
|
||||
|
||||
/* When the sticky bit is clear, in most displays,
|
||||
* notifications disappear after a certain amount of time. Sticky
|
||||
* notifications, however, remain on-screen until the user dismisses them
|
||||
* explicitly, usually by clicking them.
|
||||
*
|
||||
* Sticky notifications were introduced in Growl 0.6. Most notifications
|
||||
* should not be sticky. Not all displays support sticky notifications,
|
||||
* and the user may choose in Growl's preference pane to force the
|
||||
* notification to be sticky or non-sticky, in which case the sticky bit
|
||||
* in the notification will be ignored.
|
||||
*/
|
||||
unsigned isSticky: 1;
|
||||
|
||||
/* If this is not <code>NULL</code>, and your click callback
|
||||
* is not <code>NULL</code> either, this will be passed to the callback
|
||||
* when your notification is clicked by the user.
|
||||
*
|
||||
* Click feedback was introduced in Growl 0.6, and it is optional. Not
|
||||
* all displays support click feedback.
|
||||
*/
|
||||
CFPropertyListRef clickContext;
|
||||
|
||||
/* If this is not <code>NULL</code>, it will be called instead
|
||||
* of the Growl delegate's click callback when clickContext is
|
||||
* non-<code>NULL</code> and the notification is clicked on by the user.
|
||||
*
|
||||
* Click feedback was introduced in Growl 0.6, and it is optional. Not
|
||||
* all displays support click feedback.
|
||||
*
|
||||
* The per-notification click callback is not yet supported as of Growl
|
||||
* 0.7.
|
||||
*/
|
||||
void (*clickCallback)(CFPropertyListRef clickContext);
|
||||
|
||||
CFStringRef identifier;
|
||||
};
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Easy initialisers
|
||||
|
||||
/*! @defined InitGrowlDelegate
|
||||
* @abstract Callable macro. Initializes a Growl delegate structure to defaults.
|
||||
* @discussion Call with a pointer to a struct Growl_Delegate. All of the
|
||||
* members of the structure will be set to 0 or <code>NULL</code>, except for
|
||||
* size (which will be set to <code>sizeof(struct Growl_Delegate)</code>) and
|
||||
* referenceCount (which will be set to 1).
|
||||
*/
|
||||
#define InitGrowlDelegate(delegate) \
|
||||
do { \
|
||||
if (delegate) { \
|
||||
(delegate)->size = sizeof(struct Growl_Delegate); \
|
||||
(delegate)->applicationName = NULL; \
|
||||
(delegate)->registrationDictionary = NULL; \
|
||||
(delegate)->applicationIconData = NULL; \
|
||||
(delegate)->growlInstallationWindowTitle = NULL; \
|
||||
(delegate)->growlInstallationInformation = NULL; \
|
||||
(delegate)->growlUpdateWindowTitle = NULL; \
|
||||
(delegate)->growlUpdateInformation = NULL; \
|
||||
(delegate)->referenceCount = 1U; \
|
||||
(delegate)->retain = NULL; \
|
||||
(delegate)->release = NULL; \
|
||||
(delegate)->growlIsReady = NULL; \
|
||||
(delegate)->growlNotificationWasClicked = NULL; \
|
||||
(delegate)->growlNotificationTimedOut = NULL; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/*! @defined InitGrowlNotification
|
||||
* @abstract Callable macro. Initializes a Growl notification structure to defaults.
|
||||
* @discussion Call with a pointer to a struct Growl_Notification. All of
|
||||
* the members of the structure will be set to 0 or <code>NULL</code>, except
|
||||
* for size (which will be set to
|
||||
* <code>sizeof(struct Growl_Notification)</code>).
|
||||
*/
|
||||
#define InitGrowlNotification(notification) \
|
||||
do { \
|
||||
if (notification) { \
|
||||
(notification)->size = sizeof(struct Growl_Notification); \
|
||||
(notification)->name = NULL; \
|
||||
(notification)->title = NULL; \
|
||||
(notification)->description = NULL; \
|
||||
(notification)->iconData = NULL; \
|
||||
(notification)->priority = 0; \
|
||||
(notification)->reserved = 0U; \
|
||||
(notification)->isSticky = false; \
|
||||
(notification)->clickContext = NULL; \
|
||||
(notification)->clickCallback = NULL; \
|
||||
(notification)->identifier = NULL; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Public API
|
||||
|
||||
// @functiongroup Managing the Growl delegate
|
||||
|
||||
/*! @function Growl_SetDelegate
|
||||
* @abstract Replaces the current Growl delegate with a new one, or removes
|
||||
* the Growl delegate.
|
||||
* @param newDelegate
|
||||
* @result Returns false and does nothing else if a pointer that was passed in
|
||||
* is unsatisfactory (because it is non-<code>NULL</code>, but at least one
|
||||
* required member of it is <code>NULL</code>). Otherwise, sets or unsets the
|
||||
* delegate and returns true.
|
||||
* @discussion When <code>newDelegate</code> is non-<code>NULL</code>, sets
|
||||
* the delegate to <code>newDelegate</code>. When it is <code>NULL</code>,
|
||||
* the current delegate will be unset, and no delegate will be in place.
|
||||
*
|
||||
* It is legal for <code>newDelegate</code> to be the current delegate;
|
||||
* nothing will happen, and Growl_SetDelegate will return true. It is also
|
||||
* legal for it to be <code>NULL</code>, as described above; again, it will
|
||||
* return true.
|
||||
*
|
||||
* If there was a delegate in place before the call, Growl_SetDelegate will
|
||||
* call the old delegate's release member if it was non-<code>NULL</code>. If
|
||||
* <code>newDelegate</code> is non-<code>NULL</code>, Growl_SetDelegate will
|
||||
* call <code>newDelegate->retain</code>, and set the delegate to its return
|
||||
* value.
|
||||
*
|
||||
* If you are using Growl-WithInstaller.framework, and an older version of
|
||||
* Growl is installed on the user's system, the user will automatically be
|
||||
* prompted to update.
|
||||
*
|
||||
* GrowlApplicationBridge currently does not copy this structure, nor does it
|
||||
* retain any of the CF objects in the structure (it regards the structure as
|
||||
* a container that retains the objects when they are added and releases them
|
||||
* when they are removed or the structure is destroyed). Also,
|
||||
* GrowlApplicationBridge currently does not modify any member of the
|
||||
* structure, except possibly the referenceCount by calling the retain and
|
||||
* release members.
|
||||
*/
|
||||
GROWL_EXPORT Boolean Growl_SetDelegate(struct Growl_Delegate *newDelegate);
|
||||
|
||||
/*! @function Growl_GetDelegate
|
||||
* @abstract Returns the current Growl delegate, if any.
|
||||
* @result The current Growl delegate.
|
||||
* @discussion Returns the last pointer passed into Growl_SetDelegate, or
|
||||
* <code>NULL</code> if no such call has been made.
|
||||
*
|
||||
* This function follows standard Core Foundation reference-counting rules.
|
||||
* Because it is a Get function, not a Copy function, it will not retain the
|
||||
* delegate on your behalf. You are responsible for retaining and releasing
|
||||
* the delegate as needed.
|
||||
*/
|
||||
GROWL_EXPORT struct Growl_Delegate *Growl_GetDelegate(void);
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// @functiongroup Posting Growl notifications
|
||||
|
||||
/*! @function Growl_PostNotification
|
||||
* @abstract Posts a Growl notification.
|
||||
* @param notification The notification to post.
|
||||
* @discussion This is the preferred means for sending a Growl notification.
|
||||
* The notification name and at least one of the title and description are
|
||||
* required (all three are preferred). All other parameters may be
|
||||
* <code>NULL</code> (or 0 or false as appropriate) to accept default values.
|
||||
*
|
||||
* If using the Growl-WithInstaller framework, if Growl is not installed the
|
||||
* user will be prompted to install Growl.
|
||||
* If the user cancels, this function will have no effect until the next
|
||||
* application session, at which time when it is called the user will be
|
||||
* prompted again. The user is also given the option to not be prompted again.
|
||||
* If the user does choose to install Growl, the requested notification will
|
||||
* be displayed once Growl is installed and running.
|
||||
*/
|
||||
GROWL_EXPORT void Growl_PostNotification(const struct Growl_Notification *notification);
|
||||
|
||||
/*! @function Growl_PostNotificationWithDictionary
|
||||
* @abstract Notifies using a userInfo dictionary suitable for passing to
|
||||
* CFDistributedNotificationCenter.
|
||||
* @param userInfo The dictionary to notify with.
|
||||
* @discussion Before Growl 0.6, your application would have posted
|
||||
* notifications using CFDistributedNotificationCenter by creating a userInfo
|
||||
* dictionary with the notification data. This had the advantage of allowing
|
||||
* you to add other data to the dictionary for programs besides Growl that
|
||||
* might be listening.
|
||||
*
|
||||
* This function allows you to use such dictionaries without being restricted
|
||||
* to using CFDistributedNotificationCenter. The keys for this dictionary
|
||||
* can be found in GrowlDefines.h.
|
||||
*/
|
||||
GROWL_EXPORT void Growl_PostNotificationWithDictionary(CFDictionaryRef userInfo);
|
||||
|
||||
/*! @function Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext
|
||||
* @abstract Posts a Growl notification using parameter values.
|
||||
* @param title The title of the notification.
|
||||
* @param description The description of the notification.
|
||||
* @param notificationName The name of the notification as listed in the
|
||||
* registration dictionary.
|
||||
* @param iconData Data representing a notification icon. Can be <code>NULL</code>.
|
||||
* @param priority The priority of the notification (-2 to +2, with -2
|
||||
* being Very Low and +2 being Very High).
|
||||
* @param isSticky If true, requests that this notification wait for a
|
||||
* response from the user.
|
||||
* @param clickContext An object to pass to the clickCallback, if any. Can
|
||||
* be <code>NULL</code>, in which case the clickCallback is not called.
|
||||
* @discussion Creates a temporary Growl_Notification, fills it out with the
|
||||
* supplied information, and calls Growl_PostNotification on it.
|
||||
* See struct Growl_Notification and Growl_PostNotification for more
|
||||
* information.
|
||||
*
|
||||
* The icon data can be in any format supported by NSImage. As of Mac OS X
|
||||
* 10.3, this includes the .icns, TIFF, JPEG, GIF, PNG, PDF, and PICT formats.
|
||||
*/
|
||||
GROWL_EXPORT void Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext(
|
||||
/*inhale*/
|
||||
CFStringRef title,
|
||||
CFStringRef description,
|
||||
CFStringRef notificationName,
|
||||
CFDataRef iconData,
|
||||
signed int priority,
|
||||
Boolean isSticky,
|
||||
CFPropertyListRef clickContext);
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// @functiongroup Registering
|
||||
|
||||
/*! @function Growl_RegisterWithDictionary
|
||||
* @abstract Register your application with Growl without setting a delegate.
|
||||
* @discussion When you call this function with a dictionary,
|
||||
* GrowlApplicationBridge registers your application using that dictionary.
|
||||
* If you pass <code>NULL</code>, GrowlApplicationBridge will ask the delegate
|
||||
* (if there is one) for a dictionary, and if that doesn't work, it will look
|
||||
* in your application's bundle for an auto-discoverable plist.
|
||||
* (XXX refer to more information on that)
|
||||
*
|
||||
* If you pass a dictionary to this function, it must include the
|
||||
* <code>GROWL_APP_NAME</code> key, unless a delegate is set.
|
||||
*
|
||||
* This function is mainly an alternative to the delegate system introduced
|
||||
* with Growl 0.6. Without a delegate, you cannot receive callbacks such as
|
||||
* <code>growlIsReady</code> (since they are sent to the delegate). You can,
|
||||
* however, set a delegate after registering without one.
|
||||
*
|
||||
* This function was introduced in Growl.framework 0.7.
|
||||
* @result <code>false</code> if registration failed (e.g. if Growl isn't installed).
|
||||
*/
|
||||
GROWL_EXPORT Boolean Growl_RegisterWithDictionary(CFDictionaryRef regDict);
|
||||
|
||||
/*! @function Growl_Reregister
|
||||
* @abstract Updates your registration with Growl.
|
||||
* @discussion If your application changes the contents of the
|
||||
* GROWL_NOTIFICATIONS_ALL key in the registrationDictionary member of the
|
||||
* Growl delegate, or if it changes the value of that member, or if it
|
||||
* changes the contents of its auto-discoverable plist, call this function
|
||||
* to have Growl update its registration information for your application.
|
||||
*
|
||||
* Otherwise, this function does not normally need to be called. If you're
|
||||
* using a delegate, your application will be registered when you set the
|
||||
* delegate if both the delegate and its registrationDictionary member are
|
||||
* non-<code>NULL</code>.
|
||||
*
|
||||
* This function is now implemented using
|
||||
* <code>Growl_RegisterWithDictionary</code>.
|
||||
*/
|
||||
GROWL_EXPORT void Growl_Reregister(void);
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/*! @function Growl_SetWillRegisterWhenGrowlIsReady
|
||||
* @abstract Tells GrowlApplicationBridge to register with Growl when Growl
|
||||
* launches (or not).
|
||||
* @discussion When Growl has started listening for notifications, it posts a
|
||||
* <code>GROWL_IS_READY</code> notification on the Distributed Notification
|
||||
* Center. GrowlApplicationBridge listens for this notification, using it to
|
||||
* perform various tasks (such as calling your delegate's
|
||||
* <code>growlIsReady</code> callback, if it has one). If this function is
|
||||
* called with <code>true</code>, one of those tasks will be to reregister
|
||||
* with Growl (in the manner of <code>Growl_Reregister</code>).
|
||||
*
|
||||
* This attribute is automatically set back to <code>false</code>
|
||||
* (the default) after every <code>GROWL_IS_READY</code> notification.
|
||||
* @param flag <code>true</code> if you want GrowlApplicationBridge to register with
|
||||
* Growl when next it is ready; <code>false</code> if not.
|
||||
*/
|
||||
GROWL_EXPORT void Growl_SetWillRegisterWhenGrowlIsReady(Boolean flag);
|
||||
/*! @function Growl_WillRegisterWhenGrowlIsReady
|
||||
* @abstract Reports whether GrowlApplicationBridge will register with Growl
|
||||
* when Growl next launches.
|
||||
* @result <code>true</code> if GrowlApplicationBridge will register with
|
||||
* Growl when next it posts GROWL_IS_READY; <code>false</code> if not.
|
||||
*/
|
||||
GROWL_EXPORT Boolean Growl_WillRegisterWhenGrowlIsReady(void);
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// @functiongroup Obtaining registration dictionaries
|
||||
|
||||
/*! @function Growl_CopyRegistrationDictionaryFromDelegate
|
||||
* @abstract Asks the delegate for a registration dictionary.
|
||||
* @discussion If no delegate is set, or if the delegate's
|
||||
* <code>registrationDictionary</code> member is <code>NULL</code>, this
|
||||
* function returns <code>NULL</code>.
|
||||
*
|
||||
* This function does not attempt to clean up the dictionary in any way - for
|
||||
* example, if it is missing the <code>GROWL_APP_NAME</code> key, the result
|
||||
* will be missing it too. Use
|
||||
* <code>Growl_CreateRegistrationDictionaryByFillingInDictionary</code> or
|
||||
* <code>Growl_CreateRegistrationDictionaryByFillingInDictionaryRestrictedToKeys</code>
|
||||
* to try to fill in missing keys.
|
||||
*
|
||||
* This function was introduced in Growl.framework 0.7.
|
||||
* @result A registration dictionary.
|
||||
*/
|
||||
GROWL_EXPORT CFDictionaryRef Growl_CopyRegistrationDictionaryFromDelegate(void);
|
||||
|
||||
/*! @function Growl_CopyRegistrationDictionaryFromBundle
|
||||
* @abstract Looks in a bundle for a registration dictionary.
|
||||
* @discussion This function looks in a bundle for an auto-discoverable
|
||||
* registration dictionary file using <code>CFBundleCopyResourceURL</code>.
|
||||
* If it finds one, it loads the file using <code>CFPropertyList</code> and
|
||||
* returns the result.
|
||||
*
|
||||
* If you pass <code>NULL</code> as the bundle, the main bundle is examined.
|
||||
*
|
||||
* This function does not attempt to clean up the dictionary in any way - for
|
||||
* example, if it is missing the <code>GROWL_APP_NAME</code> key, the result
|
||||
* will be missing it too. Use
|
||||
* <code>Growl_CreateRegistrationDictionaryByFillingInDictionary:</code> or
|
||||
* <code>Growl_CreateRegistrationDictionaryByFillingInDictionaryRestrictedToKeys</code>
|
||||
* to try to fill in missing keys.
|
||||
*
|
||||
* This function was introduced in Growl.framework 0.7.
|
||||
* @result A registration dictionary.
|
||||
*/
|
||||
GROWL_EXPORT CFDictionaryRef Growl_CopyRegistrationDictionaryFromBundle(CFBundleRef bundle);
|
||||
|
||||
/*! @function Growl_CreateBestRegistrationDictionary
|
||||
* @abstract Obtains a registration dictionary, filled out to the best of
|
||||
* GrowlApplicationBridge's knowledge.
|
||||
* @discussion This function creates a registration dictionary as best
|
||||
* GrowlApplicationBridge knows how.
|
||||
*
|
||||
* First, GrowlApplicationBridge examines the Growl delegate (if there is
|
||||
* one) and gets the registration dictionary from that. If no such dictionary
|
||||
* was obtained, GrowlApplicationBridge looks in your application's main
|
||||
* bundle for an auto-discoverable registration dictionary file. If that
|
||||
* doesn't exist either, this function returns <code>NULL</code>.
|
||||
*
|
||||
* Second, GrowlApplicationBridge calls
|
||||
* <code>Growl_CreateRegistrationDictionaryByFillingInDictionary</code> with
|
||||
* whatever dictionary was obtained. The result of that function is the
|
||||
* result of this function.
|
||||
*
|
||||
* GrowlApplicationBridge uses this function when you call
|
||||
* <code>Growl_SetDelegate</code>, or when you call
|
||||
* <code>Growl_RegisterWithDictionary</code> with <code>NULL</code>.
|
||||
*
|
||||
* This function was introduced in Growl.framework 0.7.
|
||||
* @result A registration dictionary.
|
||||
*/
|
||||
GROWL_EXPORT CFDictionaryRef Growl_CreateBestRegistrationDictionary(void);
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// @functiongroup Filling in registration dictionaries
|
||||
|
||||
/*! @function Growl_CreateRegistrationDictionaryByFillingInDictionary
|
||||
* @abstract Tries to fill in missing keys in a registration dictionary.
|
||||
* @param regDict The dictionary to fill in.
|
||||
* @result The dictionary with the keys filled in.
|
||||
* @discussion This function examines the passed-in dictionary for missing keys,
|
||||
* and tries to work out correct values for them. As of 0.7, it uses:
|
||||
*
|
||||
* Key Value
|
||||
* --- -----
|
||||
* <code>GROWL_APP_NAME</code> <code>CFBundleExecutableName</code>
|
||||
* <code>GROWL_APP_ICON</code> The icon of the application.
|
||||
* <code>GROWL_APP_LOCATION</code> The location of the application.
|
||||
* <code>GROWL_NOTIFICATIONS_DEFAULT</code> <code>GROWL_NOTIFICATIONS_ALL</code>
|
||||
*
|
||||
* Keys are only filled in if missing; if a key is present in the dictionary,
|
||||
* its value will not be changed.
|
||||
*
|
||||
* This function was introduced in Growl.framework 0.7.
|
||||
*/
|
||||
GROWL_EXPORT CFDictionaryRef Growl_CreateRegistrationDictionaryByFillingInDictionary(CFDictionaryRef regDict);
|
||||
/*! @function Growl_CreateRegistrationDictionaryByFillingInDictionaryRestrictedToKeys
|
||||
* @abstract Tries to fill in missing keys in a registration dictionary.
|
||||
* @param regDict The dictionary to fill in.
|
||||
* @param keys The keys to fill in. If <code>NULL</code>, any missing keys are filled in.
|
||||
* @result The dictionary with the keys filled in.
|
||||
* @discussion This function examines the passed-in dictionary for missing keys,
|
||||
* and tries to work out correct values for them. As of 0.7, it uses:
|
||||
*
|
||||
* Key Value
|
||||
* --- -----
|
||||
* <code>GROWL_APP_NAME</code> <code>CFBundleExecutableName</code>
|
||||
* <code>GROWL_APP_ICON</code> The icon of the application.
|
||||
* <code>GROWL_APP_LOCATION</code> The location of the application.
|
||||
* <code>GROWL_NOTIFICATIONS_DEFAULT</code> <code>GROWL_NOTIFICATIONS_ALL</code>
|
||||
*
|
||||
* Only those keys that are listed in <code>keys</code> will be filled in.
|
||||
* Other missing keys are ignored. Also, keys are only filled in if missing;
|
||||
* if a key is present in the dictionary, its value will not be changed.
|
||||
*
|
||||
* This function was introduced in Growl.framework 0.7.
|
||||
*/
|
||||
GROWL_EXPORT CFDictionaryRef Growl_CreateRegistrationDictionaryByFillingInDictionaryRestrictedToKeys(CFDictionaryRef regDict, CFSetRef keys);
|
||||
|
||||
/*! @brief Tries to fill in missing keys in a notification dictionary.
|
||||
* @param notifDict The dictionary to fill in.
|
||||
* @return The dictionary with the keys filled in. This will be a separate instance from \a notifDict.
|
||||
* @discussion This function examines the \a notifDict for missing keys, and
|
||||
* tries to get them from the last known registration dictionary. As of 1.1,
|
||||
* the keys that it will look for are:
|
||||
*
|
||||
* \li <code>GROWL_APP_NAME</code>
|
||||
* \li <code>GROWL_APP_ICON</code>
|
||||
*
|
||||
* @since Growl.framework 1.1
|
||||
*/
|
||||
GROWL_EXPORT CFDictionaryRef Growl_CreateNotificationDictionaryByFillingInDictionary(CFDictionaryRef notifDict);
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// @functiongroup Querying Growl's status
|
||||
|
||||
/*! @function Growl_IsInstalled
|
||||
* @abstract Determines whether the Growl prefpane and its helper app are
|
||||
* installed.
|
||||
* @result Returns true if Growl is installed, false otherwise.
|
||||
*/
|
||||
GROWL_EXPORT Boolean Growl_IsInstalled(void);
|
||||
|
||||
/*! @function Growl_IsRunning
|
||||
* @abstract Cycles through the process list to find whether GrowlHelperApp
|
||||
* is running.
|
||||
* @result Returns true if Growl is running, false otherwise.
|
||||
*/
|
||||
GROWL_EXPORT Boolean Growl_IsRunning(void);
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// @functiongroup Launching Growl
|
||||
|
||||
/*! @typedef GrowlLaunchCallback
|
||||
* @abstract Callback to notify you that Growl is running.
|
||||
* @param context The context pointer passed to Growl_LaunchIfInstalled.
|
||||
* @discussion Growl_LaunchIfInstalled calls this callback function if Growl
|
||||
* was already running or if it launched Growl successfully.
|
||||
*/
|
||||
typedef void (*GrowlLaunchCallback)(void *context);
|
||||
|
||||
/*! @function Growl_LaunchIfInstalled
|
||||
* @abstract Launches GrowlHelperApp if it is not already running.
|
||||
* @param callback A callback function which will be called if Growl was successfully
|
||||
* launched or was already running. Can be <code>NULL</code>.
|
||||
* @param context The context pointer to pass to the callback. Can be <code>NULL</code>.
|
||||
* @result Returns true if Growl was successfully launched or was already
|
||||
* running; returns false and does not call the callback otherwise.
|
||||
* @discussion Returns true and calls the callback (if the callback is not
|
||||
* <code>NULL</code>) if the Growl helper app began launching or was already
|
||||
* running. Returns false and performs no other action if Growl could not be
|
||||
* launched (e.g. because the Growl preference pane is not properly installed).
|
||||
*
|
||||
* If <code>Growl_CreateBestRegistrationDictionary</code> returns
|
||||
* non-<code>NULL</code>, this function will register with Growl atomically.
|
||||
*
|
||||
* The callback should take a single argument; this is to allow applications
|
||||
* to have context-relevant information passed back. It is perfectly
|
||||
* acceptable for context to be <code>NULL</code>. The callback itself can be
|
||||
* <code>NULL</code> if you don't want one.
|
||||
*/
|
||||
GROWL_EXPORT Boolean Growl_LaunchIfInstalled(GrowlLaunchCallback callback, void *context);
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Constants
|
||||
|
||||
/*! @defined GROWL_PREFPANE_BUNDLE_IDENTIFIER
|
||||
* @abstract The CFBundleIdentifier of the Growl preference pane bundle.
|
||||
* @discussion GrowlApplicationBridge uses this to determine whether Growl is
|
||||
* currently installed, by searching for the Growl preference pane. Your
|
||||
* application probably does not need to use this macro itself.
|
||||
*/
|
||||
#ifndef GROWL_PREFPANE_BUNDLE_IDENTIFIER
|
||||
#define GROWL_PREFPANE_BUNDLE_IDENTIFIER CFSTR("com.growl.prefpanel")
|
||||
#endif
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif /* _GROWLAPPLICATIONBRIDGE_CARBON_H_ */
|
||||
@@ -8,21 +8,38 @@ var OS = require("os"),
|
||||
|
||||
task ("build", function()
|
||||
{
|
||||
// If sw_vers does not exist, we aren't on Mac OS X
|
||||
if (!executableExists("sw_vers"))
|
||||
OS.exit(0);
|
||||
|
||||
// No building on 10.6
|
||||
var p = OS.popen(["sw_vers", "-productVersion"]);
|
||||
|
||||
if (p.wait() === 0)
|
||||
{
|
||||
var versions = p.stdout.read().split("."),
|
||||
majorVersion = parseInt(versions[0], 10),
|
||||
minorVersion = parseInt(versions[1], 10),
|
||||
buildVersion = parseInt(versions[2], 10);
|
||||
|
||||
if (majorVersion < 10 || minorVersion < 7)
|
||||
{
|
||||
colorPrint("XcodeCapp can only be built on Mac OS X 10.7+. Please go to www.cappuccino-project.org to download the latest XcodeCapp.", "red");
|
||||
OS.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (executableExists("xcodebuild"))
|
||||
{
|
||||
var args = "-sdk macosx -alltargets -configuration Release",
|
||||
supportPath = FILE.join($BUILD_CJS_CAPPUCCINO, "support", applicationName),
|
||||
installPath = FILE.join("/", "Applications", applicationName);
|
||||
|
||||
// Remove an old symlink, the application is built directly into /Applications now.
|
||||
if (FILE.isLink(installPath))
|
||||
FILE.remove(installPath);
|
||||
|
||||
if (OS.system("xcodebuild " + args))
|
||||
OS.exit(1);
|
||||
|
||||
rm_rf(supportPath);
|
||||
FILE.mkdirs(supportPath);
|
||||
cp_r(FILE.join("build", "Release", "XcodeCapp.app"), supportPath);
|
||||
FILE.chmod(FILE.join(supportPath, "Contents", "MacOS", "XcodeCapp"), 0755);
|
||||
|
||||
OS.system(["ln", "-sf", supportPath, installPath]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// PRHEmptyGrowlDelegate.h
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Andrea D'Amore on 19/06/11.
|
||||
// Copyright 2011 by author. All rights reserved.
|
||||
//
|
||||
// taken from
|
||||
// http://groups.google.com/group/growl-development/browse_thread/thread/6b0c3fb9fa31f765
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Growl/Growl.h>
|
||||
|
||||
@interface PRHEmptyGrowlDelegate : NSObject <GrowlApplicationBridgeDelegate> {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// PRHEmptyGrowlDelegate.h
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Andrea D'Amore on 19/06/11.
|
||||
// Copyright 2011 by author. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PRHEmptyGrowlDelegate.h"
|
||||
|
||||
|
||||
@implementation PRHEmptyGrowlDelegate
|
||||
|
||||
@end
|
||||
@@ -1,19 +0,0 @@
|
||||
# xCodeCapp-Cocoa
|
||||
|
||||
xcodecapp-cocoa is a port from the original xcodecapp application. It works basically the same than this
|
||||
tools shipped with Cappuccino framework but have serveral advantages:
|
||||
|
||||
* It uses the FSEventStream system to be notified when a file changes. So no useless looping
|
||||
* It consumes about no CPU when idle
|
||||
* It allows you to choose graphically the project you want to use
|
||||
* It will keep track of your already generated project helper
|
||||
* It supports .xcodecapp-ignore
|
||||
* It uses Growl to notify you when a conversion is done.
|
||||
|
||||
# License
|
||||
|
||||
All the code is distributed under AGPL v3.0 license. The parse.j comes from Cappuccino parser and uses the Cappuccino license.
|
||||
|
||||
# Author
|
||||
|
||||
Antoine Meradal <primalmotion@archipelproject.org>
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2013 Antoine Mercadal (<primalmotion@archipelproject.org>)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface TNErrorDataView : NSView
|
||||
{
|
||||
IBOutlet NSTextField *fieldFileName;
|
||||
IBOutlet NSTextField *__strong fieldMessage;
|
||||
IBOutlet NSButton *buttonOpenFile;
|
||||
|
||||
NSString *_fullPath;
|
||||
}
|
||||
|
||||
@property (strong) NSTextField *fieldMessage;
|
||||
|
||||
- (IBAction)openFile:(id)aSender;
|
||||
|
||||
@end
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2013 Antoine Mercadal (<primalmotion@archipelproject.org>)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import "TNErrorDataView.h"
|
||||
|
||||
@implementation TNErrorDataView
|
||||
|
||||
@synthesize fieldMessage;
|
||||
|
||||
/*!
|
||||
Set the data view's object value
|
||||
@param aValue the dictionary representing the error
|
||||
*/
|
||||
- (void)setObjectValue:(NSDictionary *)aValue
|
||||
{
|
||||
[fieldMessage setStringValue:[aValue valueForKey:@"message"]];
|
||||
[fieldFileName setStringValue:[aValue valueForKey:@"file"]];
|
||||
_fullPath = [aValue valueForKey:@"path"];
|
||||
}
|
||||
|
||||
|
||||
#pragma -
|
||||
#pragma Actions
|
||||
|
||||
/*!
|
||||
Open the errored file in default editor
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)openFile:(id)aSender
|
||||
{
|
||||
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
|
||||
[workspace openFile:_fullPath];
|
||||
}
|
||||
|
||||
|
||||
#pragma -
|
||||
#pragma CPCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder*)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
fieldFileName = [aCoder decodeObjectForKey:@"fieldFileName"];
|
||||
fieldMessage = [aCoder decodeObjectForKey:@"fieldMessage"];
|
||||
buttonOpenFile = [aCoder decodeObjectForKey:@"buttonOpenFile"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder*)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:fieldFileName forKey:@"fieldFileName"];
|
||||
[aCoder encodeObject:fieldMessage forKey:@"fieldMessage"];
|
||||
[aCoder encodeObject:buttonOpenFile forKey:@"buttonOpenFile"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2011 Antoine Mercadal (<primalmotion@archipelproject.org>)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "PRHEmptyGrowlDelegate.h"
|
||||
#import "FSEventCallback.h"
|
||||
|
||||
extern NSString * const XCCDidPopulateProjectNotification;
|
||||
extern NSString * const XCCConversionStartNotification;
|
||||
extern NSString * const XCCConversionStopNotification;
|
||||
extern NSString * const XCCListeningStartNotification;
|
||||
|
||||
|
||||
@interface TNXCodeCapp : NSObject
|
||||
{
|
||||
FSEventStreamRef stream;
|
||||
NSFileManager *fm;
|
||||
NSMutableArray *errorList;
|
||||
NSMutableSet *ignoredFilePaths;
|
||||
NSNumber *lastEventId;
|
||||
NSString *currentAPIMode;
|
||||
NSString *currentProjectName;
|
||||
NSString *parserPath;
|
||||
NSString *XCodeSupportPBXPath;
|
||||
NSString *XCodeSupportProjectName;
|
||||
NSString *XCodeTemplatePBXPath;
|
||||
NSString *profilePath;
|
||||
NSString *shellPath;
|
||||
NSString *PBXModifierScriptPath;
|
||||
NSURL *currentProjectURL;
|
||||
NSURL *XCodeSupportProject;
|
||||
NSURL *XCodeSupportProjectSources;
|
||||
PRHEmptyGrowlDelegate *growlDelegateRef;
|
||||
NSObject *delegate;
|
||||
NSDate *appStartedTimestamp;
|
||||
NSMutableDictionary *pathModificationDates;
|
||||
BOOL supportsFileBasedListening;
|
||||
BOOL reactToInodeModification;
|
||||
BOOL isListening;
|
||||
BOOL isUsingFileLevelAPI;
|
||||
BOOL supportFileLevelAPI;
|
||||
}
|
||||
|
||||
@property (strong) NSObject* delegate;
|
||||
@property (strong) NSMutableArray* errorList;
|
||||
@property (strong) NSURL* XCodeSupportProject;
|
||||
@property (strong) NSURL* currentProjectURL;
|
||||
@property (strong) NSString* currentProjectName;
|
||||
@property (strong) NSString* currentAPIMode;
|
||||
@property BOOL supportsFileBasedListening;
|
||||
@property BOOL reactToInodeModification;
|
||||
@property BOOL isListening;
|
||||
@property BOOL supportFileLevelAPI;
|
||||
@property BOOL isUsingFileLevelAPI;
|
||||
|
||||
- (BOOL)isObjJFile:(NSString*)path;
|
||||
- (void)computeIgnoredPaths;
|
||||
- (BOOL)isPathMatchingIgnoredPaths:(NSString*)aPath;
|
||||
- (BOOL)isXIBFile:(NSString *)path;
|
||||
- (BOOL)isXCCIgnoreFile:(NSString *)path;
|
||||
- (BOOL)prepareXCodeSupportProject;
|
||||
- (NSURL*)shadowHeaderURLForSourceURL:(NSURL*)aSourceURL;
|
||||
- (void)cleanUpShadowsRelatedToSourceURL:(NSURL*)aSourceURL;
|
||||
- (NSURL*)shadowImplementationURLForSourceURL:(NSURL*)aSourceURL;
|
||||
- (NSURL*)sourceURLForShadowName:(NSString *)aString;
|
||||
- (void)handleFileModification:(NSString*)fullPath notify:(BOOL)shouldNotify;
|
||||
- (void)handleFileRemoval:(NSString*)fullPath;
|
||||
- (void)initializeEventStreamWithPath:(NSString*)aPath;
|
||||
- (void)stopEventStream;
|
||||
- (void)updateLastEventId:(uint64_t)eventId;
|
||||
- (void)updateUserDefaultsWithLastEventId;
|
||||
- (void)synchronizeUserDefaultsWithDisk;
|
||||
- (void)listenProjectAtPath:(NSString *)path;
|
||||
- (void)clear;
|
||||
- (void)start;
|
||||
- (void)configure;
|
||||
- (void)tidyShadowedFiles;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TNXCodeCapp (SnowLeopard)
|
||||
|
||||
- (void)updateLastModificationDate:(NSDate *)date forPath:(NSString *)path;
|
||||
- (NSDate*)lastModificationDateForPath:(NSString *)path;
|
||||
|
||||
@end
|
||||
@@ -1,882 +0,0 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2011 Antoine Mercadal (<primalmotion@archipelproject.org>)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import "TNXCodeCapp.h"
|
||||
#include "macros.h"
|
||||
|
||||
NSString * const XCCUnderscoreReplacement = @"DoNotPutThisStringInFileNameOrWorldWillDie";
|
||||
NSString * const XCCDidPopulateProjectNotification = @"XCCDidPopulateProjectNotification";
|
||||
NSString * const XCCConversionStartNotification = @"XCCConversionStartNotification";
|
||||
NSString * const XCCConversionStopNotification = @"XCCConversionStopNotification";
|
||||
NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification";
|
||||
|
||||
|
||||
@implementation TNXCodeCapp
|
||||
|
||||
@synthesize delegate;
|
||||
@synthesize errorList;
|
||||
@synthesize XCodeSupportProject;
|
||||
@synthesize currentProjectURL;
|
||||
@synthesize currentProjectName;
|
||||
@synthesize supportsFileBasedListening;
|
||||
@synthesize reactToInodeModification;
|
||||
@synthesize currentAPIMode;
|
||||
@synthesize isListening;
|
||||
@synthesize supportFileLevelAPI;
|
||||
@synthesize isUsingFileLevelAPI;
|
||||
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
/*!
|
||||
Initialize the AppController
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
errorList = [NSMutableArray arrayWithCapacity:10];
|
||||
fm = [NSFileManager defaultManager];
|
||||
ignoredFilePaths = [NSMutableSet new];
|
||||
parserPath = [[NSBundle mainBundle] pathForResource:@"parser" ofType:@"j"];
|
||||
lastEventId = [[NSUserDefaults standardUserDefaults] objectForKey:@"lastEventId"];
|
||||
appStartedTimestamp = [NSDate date];
|
||||
|
||||
[self setIsListening:NO];
|
||||
[self setIsUsingFileLevelAPI:NO];
|
||||
|
||||
SInt32 versionMajor = 0;
|
||||
SInt32 versionMinor = 0;
|
||||
Gestalt(gestaltSystemVersionMajor, &versionMajor);
|
||||
Gestalt(gestaltSystemVersionMinor, &versionMinor);
|
||||
|
||||
[self setSupportFileLevelAPI:versionMajor >= 10 && versionMinor >= 7];
|
||||
// Uncomment to simulate 10.6 mode
|
||||
// [self setSupportFileLevelAPI:NO];
|
||||
|
||||
[self configure];
|
||||
|
||||
NSString* myShell = [[[NSProcessInfo processInfo] environment] objectForKey:@"SHELL"];
|
||||
|
||||
if (myShell)
|
||||
{
|
||||
shellPath = myShell;
|
||||
}
|
||||
else
|
||||
{
|
||||
shellPath = @"/bin/bash";
|
||||
}
|
||||
|
||||
if([shellPath isEqualToString:@"/bin/bash"])
|
||||
{
|
||||
if([fm fileExistsAtPath:[@"~/.bash_profile" stringByExpandingTildeInPath]])
|
||||
profilePath = [@"source ~/.bash_profile" stringByExpandingTildeInPath];
|
||||
else if([fm fileExistsAtPath:[@"~/.bashrc" stringByExpandingTildeInPath]])
|
||||
profilePath = [@"source ~/.bashrc" stringByExpandingTildeInPath];
|
||||
else if([fm fileExistsAtPath:[@"~/.profile" stringByExpandingTildeInPath]])
|
||||
profilePath = [@"source ~/.profile" stringByExpandingTildeInPath];
|
||||
else
|
||||
profilePath = @"";
|
||||
}
|
||||
else if ([shellPath isEqualToString:@"/bin/zsh"])
|
||||
{
|
||||
if([fm fileExistsAtPath:[@"~/.zshrc" stringByExpandingTildeInPath]])
|
||||
profilePath = [@"source ~/.zshrc" stringByExpandingTildeInPath];
|
||||
else if([fm fileExistsAtPath:[@"~/.profile" stringByExpandingTildeInPath]])
|
||||
profilePath = [@"source ~/.profile" stringByExpandingTildeInPath];
|
||||
else
|
||||
profilePath = @"";
|
||||
}
|
||||
else
|
||||
{
|
||||
NSAlert *alert = [NSAlert alertWithMessageText:@"Shell not recognized."
|
||||
defaultButton:@"OK"
|
||||
alternateButton:nil
|
||||
otherButton:nil
|
||||
informativeTextWithFormat:@"You are running %@ as your shell, which is not supported. Please change your shell to either BASH or ZSH.", shellPath];
|
||||
[alert runModal];
|
||||
profilePath = @"";
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"XCCReopenLastProject"])
|
||||
return;
|
||||
|
||||
NSString *lastOpenedPath = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastOpenedPath"];
|
||||
|
||||
if (lastOpenedPath)
|
||||
{
|
||||
if ([fm fileExistsAtPath:lastOpenedPath])
|
||||
{
|
||||
[self listenProjectAtPath:[NSString stringWithFormat:@"%@/", lastOpenedPath]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"LastOpenedPath"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Project Management
|
||||
|
||||
/*!
|
||||
Check if .XcodeSupport needs to be initialized.
|
||||
If not needed, check that all J files are mirrored. If no,
|
||||
then launch conversion for missing mirrored h files
|
||||
@return YES or NO
|
||||
*/
|
||||
- (BOOL)prepareXCodeSupportProject
|
||||
{
|
||||
XCodeSupportProjectName = [NSString stringWithFormat:@"%@.xcodeproj/", currentProjectName];
|
||||
XCodeTemplatePBXPath = [[NSBundle mainBundle] pathForResource:@"project.pbxproj" ofType:@"sample"];
|
||||
XCodeSupportProject = [NSURL URLWithString:XCodeSupportProjectName relativeToURL:currentProjectURL];
|
||||
XCodeSupportProjectSources = [NSURL URLWithString:@".XcodeSupport/" relativeToURL:currentProjectURL];
|
||||
XCodeSupportPBXPath = [NSString stringWithFormat:@"%@/project.pbxproj", [XCodeSupportProject path]];
|
||||
PBXModifierScriptPath = [[NSBundle mainBundle] pathForResource:@"pbxprojModifier" ofType:@"py"];
|
||||
|
||||
|
||||
//[fm removeItemAtURL:XCodeSupportProjectSources error:nil];
|
||||
//[fm removeItemAtURL:XCodeSupportProject error:nil];
|
||||
|
||||
// create the template project if it doesn't exist
|
||||
if (![fm fileExistsAtPath:[XCodeSupportProjectSources path]])
|
||||
{
|
||||
NSLog(@"prepareXCodeSupportProject: Xcode support folder created at: %@", [XCodeSupportProject path]);
|
||||
[fm createDirectoryAtPath:[XCodeSupportProject path] withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
|
||||
DLog(@"prepareXCodeSupportProject: Copying project.pbxproj from %@ to %@", XCodeTemplatePBXPath, [XCodeSupportProject path]);
|
||||
[fm copyItemAtPath:XCodeTemplatePBXPath toPath:XCodeSupportPBXPath error:nil];
|
||||
|
||||
DLog(@"prepareXCodeSupportProject: Reading the content of the project.pbxproj");
|
||||
NSMutableString *PBXContent = [NSMutableString stringWithContentsOfFile:XCodeSupportPBXPath encoding:NSUTF8StringEncoding error:nil];
|
||||
|
||||
[PBXContent writeToFile:XCodeSupportPBXPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
||||
DLog(@"prepareXCodeSupportProject: PBX file adapted to the project");
|
||||
|
||||
[self createXcodeSupportProjectSourcesDirIfNecessary];
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self createXcodeSupportProjectSourcesDirIfNecessary];
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Create the .XcodeSupport/Sources folder if necessary.
|
||||
*/
|
||||
- (void)createXcodeSupportProjectSourcesDirIfNecessary
|
||||
{
|
||||
if ([fm fileExistsAtPath:[XCodeSupportProjectSources path]])
|
||||
return;
|
||||
|
||||
DLog(@"createXcodeSupportProjectSourcesDirIfNecessary: Creating source folder %@", [XCodeSupportProjectSources path]);
|
||||
[fm createDirectoryAtPath:[XCodeSupportProjectSources path] withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initialize the creation of the .XcodeSupport project. This
|
||||
Operation is threaded
|
||||
@param arguments Thread arguments (not used)
|
||||
@param shouldNotify is YES, XCCDidPopulateProjectNotification will be send
|
||||
*/
|
||||
- (void)populateXCodeProject:(NSNumber *)shouldNotify
|
||||
{
|
||||
if ([shouldNotify boolValue])
|
||||
[delegate performSelector:@selector(growlWithTitle:message:) withObject:@"Loading project" withObject:[currentProjectURL path]];
|
||||
|
||||
NSArray *subdpaths = [fm subpathsAtPath:[currentProjectURL path]];
|
||||
|
||||
for (NSString *p in subdpaths)
|
||||
{
|
||||
NSString *filePath = [NSString stringWithFormat:@"%@/%@", [currentProjectURL path], p];
|
||||
|
||||
BOOL isDir = NO;
|
||||
[fm fileExistsAtPath:filePath isDirectory:&isDir];
|
||||
|
||||
if (isDir || ![self isObjJFile:filePath] || [self isPathMatchingIgnoredPaths:filePath])
|
||||
continue;
|
||||
|
||||
NSURL *eventualShadow = [self shadowHeaderURLForSourceURL:[NSURL fileURLWithPath:filePath]];
|
||||
|
||||
if (![fm fileExistsAtPath:[eventualShadow path]])
|
||||
{
|
||||
DLog(@"populateXCodeProject: Computing missing shadow file for %@", filePath);
|
||||
[self handleFileModification:filePath notify:NO];
|
||||
}
|
||||
}
|
||||
|
||||
if ([shouldNotify boolValue])
|
||||
{
|
||||
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:currentProjectURL, @"URL", nil];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:XCCDidPopulateProjectNotification object:self userInfo:info];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Start all needed processes for listening to a given path
|
||||
@param path The folder path to listen to
|
||||
*/
|
||||
- (void)listenProjectAtPath:(NSString *)path
|
||||
{
|
||||
NSMutableString *tempName = [NSMutableString stringWithString:[path lastPathComponent]];
|
||||
|
||||
currentProjectURL = [NSURL fileURLWithPath:path];
|
||||
|
||||
[tempName replaceOccurrencesOfString:@" "
|
||||
withString:@"_"
|
||||
options:NSCaseInsensitiveSearch
|
||||
range:NSMakeRange(0, [tempName length])];
|
||||
currentProjectName = [NSString stringWithString:tempName];
|
||||
|
||||
[self computeIgnoredPaths];
|
||||
|
||||
BOOL isProjectReady = [self prepareXCodeSupportProject];
|
||||
|
||||
[NSThread detachNewThreadSelector:@selector(populateXCodeProject:) toTarget:self withObject:[NSNumber numberWithBool:!isProjectReady]];
|
||||
|
||||
[self initializeEventStreamWithPath:[currentProjectURL path]];
|
||||
|
||||
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:path, @"path", [NSNumber numberWithInt:(isProjectReady) ? 1 : 0], @"ready", nil];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:XCCListeningStartNotification object:self userInfo:info];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[currentProjectURL path] forKey:@"LastOpenedPath"];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Event Stream
|
||||
|
||||
/*!
|
||||
Initializes the FSEvent stream
|
||||
@param aPath the path of the folder to listen
|
||||
*/
|
||||
- (void)initializeEventStreamWithPath:(NSString*)aPath
|
||||
{
|
||||
if ([self isListening])
|
||||
return;
|
||||
|
||||
[self stopEventStream];
|
||||
|
||||
NSMutableArray *pathsToWatch = [NSMutableArray arrayWithObject:aPath];
|
||||
void *appPointer = (__bridge void *)self;
|
||||
FSEventStreamContext context = {0, appPointer, NULL, NULL, NULL};
|
||||
CFTimeInterval latency = 2.0;
|
||||
FSEventStreamCreateFlags flags = 0;
|
||||
|
||||
if (supportsFileBasedListening)
|
||||
{
|
||||
DLog(@"initializeEventStreamWithPath: Initializing the FSEventStream at file level (clean)");
|
||||
flags = kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"Initializing the FSEventStream at folder level (dirty)");
|
||||
flags = kFSEventStreamCreateFlagUseCFTypes;
|
||||
}
|
||||
|
||||
// add symlinked directories
|
||||
NSArray *fileList = [fm contentsOfDirectoryAtPath:aPath error:nil];
|
||||
|
||||
for (NSString *node in fileList)
|
||||
{
|
||||
NSDictionary *attributes = [fm attributesOfItemAtPath:aPath error:nil];
|
||||
if ([[attributes objectForKey:@"NSFileType"] isEqualTo:NSFileTypeDirectory])
|
||||
{
|
||||
NSString *subDirectoryPath = [aPath stringByAppendingPathComponent:node];
|
||||
NSString *symlinkDestination = [fm destinationOfSymbolicLinkAtPath:subDirectoryPath error:nil];
|
||||
|
||||
if (symlinkDestination)
|
||||
{
|
||||
[pathsToWatch addObject:subDirectoryPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream = FSEventStreamCreate(NULL, &fsevents_callback, &context, (__bridge CFArrayRef) pathsToWatch,
|
||||
[lastEventId unsignedLongLongValue], latency, flags);
|
||||
|
||||
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
FSEventStreamStart(stream);
|
||||
[self setIsListening:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Stop listening the FSEvent stream if active
|
||||
*/
|
||||
- (void)stopEventStream
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
FSEventStreamStop(stream);
|
||||
FSEventStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
FSEventStreamInvalidate(stream);
|
||||
FSEventStreamRelease(stream);
|
||||
stream = NULL;
|
||||
}
|
||||
|
||||
[self setIsListening:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
Stops and clear the worker
|
||||
*/
|
||||
- (void)clear
|
||||
{
|
||||
[self updateUserDefaultsWithLastEventId];
|
||||
[self synchronizeUserDefaultsWithDisk];
|
||||
currentProjectURL = nil;
|
||||
currentProjectName = nil;
|
||||
[ignoredFilePaths removeAllObjects];
|
||||
[self stopEventStream];
|
||||
}
|
||||
|
||||
/*!
|
||||
Choose the API mode according to default
|
||||
*/
|
||||
- (void)configure
|
||||
{
|
||||
if (![self supportFileLevelAPI])
|
||||
{
|
||||
DLog(@"configure: System doesn't support file level API");
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:2] forKey:@"XCCAPIMode"];
|
||||
}
|
||||
|
||||
switch ([[NSUserDefaults standardUserDefaults] integerForKey:@"XCCAPIMode"])
|
||||
{
|
||||
case 0:
|
||||
supportsFileBasedListening = [self supportFileLevelAPI] ? YES : NO;
|
||||
break;
|
||||
case 1:
|
||||
supportsFileBasedListening = YES;
|
||||
break;
|
||||
case 2:
|
||||
supportsFileBasedListening = NO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (supportsFileBasedListening)
|
||||
{
|
||||
DLog(@"configure: using 10.7+ mode listening (clean)");
|
||||
|
||||
[self setCurrentAPIMode:@"File level (Lion)"];
|
||||
[self setIsUsingFileLevelAPI:YES];
|
||||
reactToInodeModification = [[NSUserDefaults standardUserDefaults] boolForKey:@"XCCReactMode"];
|
||||
}
|
||||
else
|
||||
{
|
||||
DLog(@"configure: using 10.6 mode listening (dirty)");
|
||||
reactToInodeModification = NO;
|
||||
[self setCurrentAPIMode:@"Folder level (Snow Leopard)"];
|
||||
[self setIsUsingFileLevelAPI:NO];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Update the last event ID. We use a method because
|
||||
This is called from outside the class, in the FSEvent callback
|
||||
@param eventId the current event ID value
|
||||
*/
|
||||
- (void)updateLastEventId:(uint64_t)eventId
|
||||
{
|
||||
lastEventId = [NSNumber numberWithUnsignedLongLong:eventId];
|
||||
}
|
||||
|
||||
/*!
|
||||
Updates the user defaults with the last recorded event Id.
|
||||
*/
|
||||
- (void)updateUserDefaultsWithLastEventId
|
||||
{
|
||||
if (lastEventId && [lastEventId longLongValue] != 0)
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:lastEventId forKey:@"lastEventId"];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Tells the standard user defaults to synchronize with disk.
|
||||
*/
|
||||
- (void)synchronizeUserDefaultsWithDisk
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Shell Helpers
|
||||
|
||||
/*!
|
||||
Run a NSTask with the given arguments
|
||||
@param arguments NSArray containing the NSTask arguments
|
||||
@return NSarray containing the return code (int) and the eventual response (string)
|
||||
*/
|
||||
- (NSArray *)runTask:(NSArray *)arguments
|
||||
{
|
||||
NSTask *task;
|
||||
NSData *stdOut;
|
||||
NSString *response;
|
||||
NSNumber *status;
|
||||
|
||||
task = [[NSTask alloc] init];
|
||||
|
||||
[task setLaunchPath:shellPath];
|
||||
[task setArguments: arguments];
|
||||
[task setStandardOutput:[NSPipe pipe]];
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
|
||||
stdOut = [[[task standardOutput] fileHandleForReading] availableData];
|
||||
response = [[NSString alloc] initWithData:stdOut encoding:NSUTF8StringEncoding];
|
||||
status = [NSNumber numberWithInt:[task terminationStatus]];
|
||||
|
||||
return [NSArray arrayWithObjects:status, response, nil];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Event Handlers
|
||||
|
||||
/*!
|
||||
Handle a file modification. If it's a .J or XIB or NIB, it will
|
||||
perform the according conversion. If it's .xcodecapp-ignore, it will
|
||||
update the list of ignored files.
|
||||
@param fullPath the full path of the modified file
|
||||
@param shouldNotify if YES, Growl notifications will be displayed
|
||||
*/
|
||||
- (void)handleFileModification:(NSString*)fullPath notify:(BOOL)shouldNotify
|
||||
{
|
||||
if (![self isXIBFile:fullPath] && ![self isObjJFile:fullPath] && ![self isXCCIgnoreFile:fullPath])
|
||||
return;
|
||||
|
||||
if ([self isPathMatchingIgnoredPaths:fullPath] || ![fm fileExistsAtPath:fullPath])
|
||||
return;
|
||||
|
||||
DLog(@"handleFileModification:notify: Parsing modified file: %@", fullPath);
|
||||
|
||||
NSArray *arguments = nil;
|
||||
NSArray *PBXArguments = nil;
|
||||
NSString *successTitle = nil;
|
||||
NSString *successMsg = nil;
|
||||
NSString *response = nil;
|
||||
NSNumber *status = [NSNumber numberWithInt:0];
|
||||
NSString *splitPath = [fullPath substringFromIndex:[[currentProjectURL path] length] + 1];
|
||||
NSURL *encodedURL = [NSURL URLWithString:[fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
|
||||
NSURL *shadowHeaderURL = [self shadowHeaderURLForSourceURL:encodedURL];
|
||||
NSURL *shadowImplementationURL = [self shadowImplementationURLForSourceURL:encodedURL];
|
||||
|
||||
DLog(@"handleFileModification:notify: Shadow header path: %@", shadowHeaderURL);
|
||||
DLog(@"handleFileModification:notify: Shadow implementation path: %@", shadowImplementationURL);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:XCCConversionStartNotification object:self];
|
||||
|
||||
if ([self isXIBFile:fullPath])
|
||||
{
|
||||
arguments = [NSArray arrayWithObjects: @"-c",
|
||||
[NSString stringWithFormat:@"(%@; nib2cib '%@';) 2>&1", profilePath, fullPath],@"",nil];
|
||||
|
||||
successTitle = @"XIB converted";
|
||||
successMsg = splitPath;
|
||||
}
|
||||
else if ([self isObjJFile:fullPath])
|
||||
{
|
||||
arguments = [NSArray arrayWithObjects: @"-c",
|
||||
[NSString stringWithFormat:@"(%@; objj '%@' '%@' '%@' '%@';) 2>&1",
|
||||
profilePath,
|
||||
parserPath,
|
||||
fullPath,
|
||||
[shadowHeaderURL path],
|
||||
[shadowImplementationURL path]],nil];
|
||||
|
||||
PBXArguments = [NSArray arrayWithObjects: @"-c",
|
||||
[NSString stringWithFormat:@"(%@; python %@ add '%@' '%@' '%@' '%@' '%@') 2>&1",
|
||||
profilePath,
|
||||
PBXModifierScriptPath,
|
||||
XCodeSupportPBXPath,
|
||||
[shadowHeaderURL path],
|
||||
[shadowImplementationURL path],
|
||||
fullPath,
|
||||
[currentProjectURL path]],nil];
|
||||
|
||||
successTitle = @"Objective-J source processed";
|
||||
successMsg = splitPath;
|
||||
}
|
||||
else if ([self isXCCIgnoreFile:fullPath])
|
||||
{
|
||||
[self computeIgnoredPaths];
|
||||
successTitle = @".xcodecapp-ignore processed";
|
||||
successMsg = @"Ignored files list updated";
|
||||
arguments = nil;
|
||||
}
|
||||
|
||||
// Run the task and get the response if needed
|
||||
if (arguments)
|
||||
{
|
||||
DLog(@"handleFileModification:notify: Running conversion task...");
|
||||
NSArray *statusInfo = [self runTask:arguments];
|
||||
|
||||
status = [statusInfo objectAtIndex:0];
|
||||
response = [statusInfo objectAtIndex:1];
|
||||
|
||||
DLog(@"handleFileModification:notify: Conversion task result/response: %@/%@", status, response);
|
||||
|
||||
if ([status intValue] == 0 && shouldNotify)
|
||||
{
|
||||
[delegate performSelector:@selector(growlWithTitle:message:) withObject:successTitle withObject:successMsg];
|
||||
}
|
||||
else if (![status intValue] == 0)
|
||||
{
|
||||
if (response)
|
||||
{
|
||||
NSDictionary *errorDictionary = [NSDictionary dictionaryWithObjectsAndKeys:response, @"message",
|
||||
splitPath, @"file",
|
||||
fullPath, @"path", nil];
|
||||
|
||||
[errorList addObject:errorDictionary];
|
||||
}
|
||||
|
||||
[delegate performSelector:@selector(growlWithTitle:message:) withObject:@"Error processing file" withObject:splitPath];
|
||||
}
|
||||
}
|
||||
|
||||
if (PBXArguments)
|
||||
{
|
||||
DLog(@"handleFileModification:notify: Running update PBX task...");
|
||||
NSArray *statusInfo = [self runTask:PBXArguments];
|
||||
status = [statusInfo objectAtIndex:0];
|
||||
response = [statusInfo objectAtIndex:1];
|
||||
DLog(@"handleFileModification:notify: Update PBX Task result/response: %@/%@", status, response);
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:XCCConversionStopNotification object:self];
|
||||
DLog(@"handleFileModification:notify: Processed: %@", fullPath);
|
||||
}
|
||||
|
||||
/*!
|
||||
Handle a file deletion. If it's a .J, it will
|
||||
remove the shadowed .h file. If it's .xcodecapp-ignore
|
||||
it will reset the list of ignored files.
|
||||
@param fullPath the full path of the modified file
|
||||
@param shouldNotify if YES, Growl notifications will be displayed
|
||||
*/
|
||||
- (void)handleFileRemoval:(NSString*)fullPath
|
||||
{
|
||||
if ([self isPathMatchingIgnoredPaths:fullPath] || [fm fileExistsAtPath:fullPath])
|
||||
return;
|
||||
|
||||
NSString *encodedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
if ([self isObjJFile:fullPath])
|
||||
[self cleanUpShadowsRelatedToSourceURL:[NSURL URLWithString:encodedPath]];
|
||||
else if ([self isXCCIgnoreFile:fullPath])
|
||||
[self computeIgnoredPaths];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Source Files Management
|
||||
|
||||
/*!
|
||||
Check if given full path is Objective-J file
|
||||
@param path the path to check
|
||||
@return YES or NO
|
||||
*/
|
||||
- (BOOL)isObjJFile:(NSString *)path
|
||||
{
|
||||
return [[[path pathExtension] uppercaseString] isEqual:@"J"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Check if given full path is XIB or NIB file
|
||||
@param path the path to check
|
||||
@return YES or NO
|
||||
*/
|
||||
- (BOOL)isXIBFile:(NSString *)path
|
||||
{
|
||||
path = [[path pathExtension] uppercaseString];
|
||||
return [path isEqual:@"XIB"] || [path isEqual:@"NIB"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Check if given full path is the .xcodecapp-ignore file
|
||||
@param path the path to check
|
||||
@return YES or NO
|
||||
*/
|
||||
- (BOOL)isXCCIgnoreFile:(NSString *)path
|
||||
{
|
||||
path = [path lastPathComponent];
|
||||
return [path isEqual:@".xcodecapp-ignore"];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Shadow Files Management
|
||||
|
||||
/*!
|
||||
Compute the mirorred (shadow) header file name for a given path
|
||||
@param aSourceURL the origin path
|
||||
@return NSURL representing the shadow URL for the header file
|
||||
*/
|
||||
- (NSURL *)shadowHeaderURLForSourceURL:(NSURL*)aSourceURL
|
||||
{
|
||||
if (!aSourceURL)
|
||||
[NSException raise:NSInvalidArgumentException format:@"shadowHeaderURLForSourceURL: aSource URL must not be null"];
|
||||
|
||||
NSMutableString *flattenedPath = [NSMutableString stringWithString:[aSourceURL path]];
|
||||
|
||||
// Replace "_" with a substring that is unlikely to be in a filename
|
||||
[flattenedPath replaceOccurrencesOfString:@"_"
|
||||
withString:XCCUnderscoreReplacement
|
||||
options:0
|
||||
range:NSMakeRange(0, [flattenedPath length])];
|
||||
|
||||
[flattenedPath replaceOccurrencesOfString:@"/"
|
||||
withString:@"_"
|
||||
options:0
|
||||
range:NSMakeRange(0, [flattenedPath length])];
|
||||
|
||||
DLog(@"shadowHeaderURLForSourceURL: Flattened path: %@", flattenedPath);
|
||||
NSString *basename = [NSString stringWithFormat:@"%@.h", [[flattenedPath stringByDeletingPathExtension] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
|
||||
|
||||
return [NSURL URLWithString:basename relativeToURL:XCodeSupportProjectSources];
|
||||
}
|
||||
|
||||
/*!
|
||||
Compute the mirorred (shadow) implementation file name for a given path
|
||||
@param aSourceURL the origin path
|
||||
@return NSURL representing the shadow URL for the implementation file
|
||||
*/
|
||||
- (NSURL *)shadowImplementationURLForSourceURL:(NSURL*)aSourceURL
|
||||
{
|
||||
if (!aSourceURL)
|
||||
[NSException raise:NSInvalidArgumentException format:@"shadowImplementationURLForSourceURL: aSource URL must not be null"];
|
||||
|
||||
NSURL *shadowHeaderURL = [self shadowHeaderURLForSourceURL:aSourceURL];
|
||||
NSURL *shadowImplPath = [[shadowHeaderURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"m"];
|
||||
|
||||
return shadowImplPath;
|
||||
}
|
||||
|
||||
/*!
|
||||
Compute the Cappuccino source file that is related to the given shadow header file URL
|
||||
@param aString the origin path
|
||||
@return NSURL representing the URL for the related Cappuccino source file
|
||||
*/
|
||||
- (NSURL *)sourceURLForShadowName:(NSString *)aString
|
||||
{
|
||||
NSMutableString * unshadowedPath = [NSMutableString stringWithString:aString];
|
||||
|
||||
[unshadowedPath replaceOccurrencesOfString:@"_"
|
||||
withString:@"/"
|
||||
options:0
|
||||
range:NSMakeRange(0, [unshadowedPath length])];
|
||||
|
||||
[unshadowedPath replaceOccurrencesOfString:XCCUnderscoreReplacement
|
||||
withString:@"_"
|
||||
options:0
|
||||
range:NSMakeRange(0, [unshadowedPath length])];
|
||||
|
||||
[unshadowedPath replaceOccurrencesOfString:@".h"
|
||||
withString:@".j"
|
||||
options:0
|
||||
range:NSMakeRange(0, [unshadowedPath length])];
|
||||
|
||||
return [NSURL fileURLWithPath:[NSString stringWithString:unshadowedPath]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Clean up any shadow files and PBX entries related to given the Cappuccino source file URL
|
||||
@param anURL the Cappuccino source file URL
|
||||
*/
|
||||
- (void)cleanUpShadowsRelatedToSourceURL:(NSURL *)anURL
|
||||
{
|
||||
NSURL *shadowHeaderURL = [self shadowHeaderURLForSourceURL:anURL];
|
||||
NSURL *shadowImplementationURL = [self shadowImplementationURLForSourceURL:anURL];
|
||||
|
||||
DLog(@"cleanUpShadowsRelatedToSourceURL: Removing shadow header file: %@", shadowHeaderURL);
|
||||
[fm removeItemAtURL:shadowHeaderURL error:nil];
|
||||
|
||||
DLog(@"cleanUpShadowsRelatedToSourceURL: Removing shadow implementation file: %@", shadowImplementationURL);
|
||||
[fm removeItemAtURL:shadowImplementationURL error:nil];
|
||||
|
||||
DLog(@"cleanUpShadowsRelatedToSourceURL:Removing PBX reference task...");
|
||||
NSArray *PBXArguments = [NSArray arrayWithObjects: @"-c",
|
||||
[NSString stringWithFormat:@"(%@; python %@ remove '%@' '%@' '%@' '%@' '%@') 2>&1",
|
||||
profilePath,
|
||||
PBXModifierScriptPath,
|
||||
XCodeSupportPBXPath,
|
||||
[shadowHeaderURL path],
|
||||
[shadowImplementationURL path],
|
||||
[anURL path],
|
||||
[currentProjectURL path]],nil];
|
||||
|
||||
NSArray *statusInfo = [self runTask:PBXArguments];
|
||||
NSNumber *status = [statusInfo objectAtIndex:0];
|
||||
NSString *response = [statusInfo objectAtIndex:1];
|
||||
DLog(@"cleanUpShadowsRelatedToSourceURL: PBX Reference removal status/response: %@/%@", status, response);
|
||||
}
|
||||
|
||||
/*!
|
||||
Clean the support folder according to files present in given path
|
||||
*/
|
||||
- (void)tidyShadowedFiles
|
||||
{
|
||||
NSArray *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[XCodeSupportProjectSources path] error:NULL];
|
||||
|
||||
for (NSString *subpath in subpaths)
|
||||
{
|
||||
if (![[subpath pathExtension] isEqual:@".h"] || [[subpath lastPathComponent] isEqual:@"xcc_general_include.h"])
|
||||
continue;
|
||||
|
||||
NSURL *unshadowed = [self sourceURLForShadowName:subpath];
|
||||
|
||||
if (![fm fileExistsAtPath:[unshadowed path]])
|
||||
{
|
||||
[self cleanUpShadowsRelatedToSourceURL:unshadowed];
|
||||
|
||||
if (![self supportFileLevelAPI] && [self respondsToSelector:@selector(updateLastModificationDate:forPath:)])
|
||||
[self performSelector:@selector(updateLastModificationDate:forPath:) withObject:nil withObject:unshadowed];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - XCC Ignore management
|
||||
|
||||
/*!
|
||||
Compute the ignored paths according to any existing
|
||||
.xcodecapp-ignore file
|
||||
*/
|
||||
- (void)computeIgnoredPaths
|
||||
{
|
||||
NSString *ignorePath = [NSString stringWithFormat:@"%@/.xcodecapp-ignore", [currentProjectURL path]];
|
||||
[ignoredFilePaths removeAllObjects];
|
||||
|
||||
if ([fm fileExistsAtPath:ignorePath])
|
||||
{
|
||||
NSString *ignoreFileContent = [NSString stringWithContentsOfFile:ignorePath encoding:NSUTF8StringEncoding error:nil];
|
||||
NSArray *ignoredPatterns = [ignoreFileContent componentsSeparatedByString:@"\n"];
|
||||
|
||||
for (NSString *pattern in ignoredPatterns)
|
||||
{
|
||||
if ([pattern length])
|
||||
[ignoredFilePaths addObject:pattern];
|
||||
}
|
||||
}
|
||||
|
||||
[ignoredFilePaths addObject:@"*/.git/*"];
|
||||
[ignoredFilePaths addObject:@"*/.svn/*"];
|
||||
[ignoredFilePaths addObject:@"*/.hg/*"];
|
||||
[ignoredFilePaths addObject:@"*/Frameworks/*"];
|
||||
[ignoredFilePaths addObject:@"*/.XcodeSupport/*"];
|
||||
[ignoredFilePaths addObject:@"*/Build/*"];
|
||||
[ignoredFilePaths addObject:@"*/NS_*.j"];
|
||||
[ignoredFilePaths addObject:@"*main.j"];
|
||||
[ignoredFilePaths addObject:@"*.xcodeproj/*"];
|
||||
[ignoredFilePaths addObject:@"*.DS_Store"];
|
||||
|
||||
NSLog(@"Ignoring file paths: %@", ignoredFilePaths);
|
||||
}
|
||||
|
||||
/*!
|
||||
Check is given path should be ignored
|
||||
@param aPath the path to check
|
||||
@return YES if it should be ignored, NO otherwise
|
||||
*/
|
||||
- (BOOL)isPathMatchingIgnoredPaths:(NSString*)aPath
|
||||
{
|
||||
if ([ignoredFilePaths count] == 0)
|
||||
return NO;
|
||||
|
||||
for (NSString *ignoredPath in ignoredFilePaths)
|
||||
{
|
||||
if ([ignoredPath length] == 0)
|
||||
continue;
|
||||
|
||||
NSMutableString *regexp = [ignoredPath mutableCopy];
|
||||
|
||||
[regexp replaceOccurrencesOfString:@"/"
|
||||
withString:@"\\/"
|
||||
options:0
|
||||
range:NSMakeRange(0, [regexp length])];
|
||||
|
||||
[regexp replaceOccurrencesOfString:@"."
|
||||
withString:@"\\."
|
||||
options:0
|
||||
range:NSMakeRange(0, [regexp length])];
|
||||
|
||||
[regexp replaceOccurrencesOfString:@"*"
|
||||
withString:@".*"
|
||||
options:0
|
||||
range:NSMakeRange(0, [regexp length])];
|
||||
|
||||
[regexp replaceOccurrencesOfString:@" "
|
||||
withString:@"\\ "
|
||||
options:0
|
||||
range:NSMakeRange(0, [regexp length])];
|
||||
|
||||
NSPredicate *regextest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regexp];
|
||||
|
||||
if ([regextest evaluateWithObject:aPath])
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TNXCodeCapp (SnowLeopard)
|
||||
|
||||
- (void)updateLastModificationDate:(NSDate *)date forPath:(NSString *)path
|
||||
{
|
||||
if (!pathModificationDates)
|
||||
{
|
||||
pathModificationDates = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"pathModificationDates"] mutableCopy];
|
||||
|
||||
if (!pathModificationDates)
|
||||
pathModificationDates = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
if (date)
|
||||
[pathModificationDates setObject:date forKey:path];
|
||||
else
|
||||
[pathModificationDates removeObjectForKey:path];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setObject:pathModificationDates forKey:@"pathModificationDates"];
|
||||
}
|
||||
|
||||
- (NSDate *)lastModificationDateForPath:(NSString *)path
|
||||
{
|
||||
if (!pathModificationDates)
|
||||
{
|
||||
pathModificationDates = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"pathModificationDates"] mutableCopy];
|
||||
|
||||
if (!pathModificationDates)
|
||||
pathModificationDates = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
if ([pathModificationDates valueForKey:path] != nil)
|
||||
return [pathModificationDates valueForKey:path];
|
||||
else
|
||||
return appStartedTimestamp;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,152 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
29B97313FDCFA39411CA2CEA /* Project object */ = {
|
||||
activeArchitecture = i386;
|
||||
activeBuildConfigurationName = Debug;
|
||||
activeExecutable = 8E01A8CD0DE519E70008BB35 /* xcodecapp-cocoa */;
|
||||
activeTarget = 8D1107260486CEB800E47090 /* xcodecapp-cocoa */;
|
||||
addToTargets = (
|
||||
8D1107260486CEB800E47090 /* xcodecapp-cocoa */,
|
||||
);
|
||||
breakpoints = (
|
||||
8EF107B90DFCF23200C52EB1 /* AppController.m:30 */,
|
||||
8EF107CA0DFCF4A100C52EB1 /* AppController.m:62 */,
|
||||
);
|
||||
codeSenseManager = 8E01A8DC0DE51A060008BB35 /* Code sense */;
|
||||
executables = (
|
||||
8E01A8CD0DE519E70008BB35 /* xcodecapp-cocoa */,
|
||||
);
|
||||
perUserDictionary = {
|
||||
PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = {
|
||||
PBXFileTableDataSourceColumnSortingDirectionKey = "-1";
|
||||
PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID;
|
||||
PBXFileTableDataSourceColumnWidthsKey = (
|
||||
20,
|
||||
71,
|
||||
20,
|
||||
48,
|
||||
43,
|
||||
43,
|
||||
20,
|
||||
);
|
||||
PBXFileTableDataSourceColumnsKey = (
|
||||
PBXFileDataSource_FiletypeID,
|
||||
PBXFileDataSource_Filename_ColumnID,
|
||||
PBXFileDataSource_Built_ColumnID,
|
||||
PBXFileDataSource_ObjectSize_ColumnID,
|
||||
PBXFileDataSource_Errors_ColumnID,
|
||||
PBXFileDataSource_Warnings_ColumnID,
|
||||
PBXFileDataSource_Target_ColumnID,
|
||||
);
|
||||
};
|
||||
PBXPerProjectTemplateStateSaveDate = 244845075;
|
||||
PBXWorkspaceStateSaveDate = 244845075;
|
||||
};
|
||||
sourceControlManager = 8E01A8DB0DE51A060008BB35 /* Source Control */;
|
||||
userBuildSettings = {
|
||||
};
|
||||
};
|
||||
29B97316FDCFA39411CA2CEA /* main.m */ = {
|
||||
uiCtxt = {
|
||||
sepNavIntBoundsRect = "{{0, 0}, {1176, 392}}";
|
||||
sepNavSelRange = "{818, 0}";
|
||||
sepNavVisRange = "{297, 521}";
|
||||
};
|
||||
};
|
||||
8D1107260486CEB800E47090 /* xcodecapp-cocoa */ = {
|
||||
activeExec = 0;
|
||||
executables = (
|
||||
8E01A8CD0DE519E70008BB35 /* xcodecapp-cocoa */,
|
||||
);
|
||||
};
|
||||
8E01A8CD0DE519E70008BB35 /* xcodecapp-cocoa */ = {
|
||||
isa = PBXExecutable;
|
||||
activeArgIndices = (
|
||||
);
|
||||
argumentStrings = (
|
||||
);
|
||||
autoAttachOnCrash = 1;
|
||||
breakpointsEnabled = 0;
|
||||
configStateDict = {
|
||||
};
|
||||
customDataFormattersEnabled = 1;
|
||||
debuggerPlugin = GDBDebugging;
|
||||
disassemblyDisplayState = 0;
|
||||
dylibVariantSuffix = "";
|
||||
enableDebugStr = 1;
|
||||
environmentEntries = (
|
||||
);
|
||||
executableSystemSymbolLevel = 0;
|
||||
executableUserSymbolLevel = 0;
|
||||
libgmallocEnabled = 0;
|
||||
name = xcodecapp-cocoa;
|
||||
savedGlobals = {
|
||||
};
|
||||
sourceDirectories = (
|
||||
);
|
||||
variableFormatDictionary = {
|
||||
"*eventIds-long long unsigned int-mycallback" = 3;
|
||||
};
|
||||
};
|
||||
8E01A8DB0DE51A060008BB35 /* Source Control */ = {
|
||||
isa = PBXSourceControlManager;
|
||||
fallbackIsa = XCSourceControlManager;
|
||||
isSCMEnabled = 0;
|
||||
scmConfiguration = {
|
||||
};
|
||||
};
|
||||
8E01A8DC0DE51A060008BB35 /* Code sense */ = {
|
||||
isa = PBXCodeSenseManager;
|
||||
indexTemplatePath = "";
|
||||
};
|
||||
8E01A91D0DE9EED20008BB35 /* AppController.h */ = {
|
||||
uiCtxt = {
|
||||
sepNavIntBoundsRect = "{{0, 0}, {692, 280}}";
|
||||
sepNavSelRange = "{234, 37}";
|
||||
sepNavVisRange = "{146, 185}";
|
||||
sepNavWindowFrame = "{{112, 264}, {1441, 774}}";
|
||||
};
|
||||
};
|
||||
8E01A91E0DE9EED20008BB35 /* AppController.m */ = {
|
||||
uiCtxt = {
|
||||
sepNavIntBoundsRect = "{{0, 0}, {692, 2324}}";
|
||||
sepNavSelRange = "{4949, 0}";
|
||||
sepNavVisRange = "{3760, 551}";
|
||||
sepNavWindowFrame = "{{15, -1}, {1441, 774}}";
|
||||
};
|
||||
};
|
||||
8EF107B90DFCF23200C52EB1 /* AppController.m:30 */ = {
|
||||
isa = PBXFileBreakpoint;
|
||||
actions = (
|
||||
);
|
||||
breakpointStyle = 0;
|
||||
continueAfterActions = 0;
|
||||
countType = 0;
|
||||
delayBeforeContinue = 0;
|
||||
fileReference = 8E01A91E0DE9EED20008BB35 /* AppController.m */;
|
||||
functionName = "-awakeFromNib";
|
||||
hitCount = 1;
|
||||
ignoreCount = 0;
|
||||
lineNumber = 30;
|
||||
location = xcodecapp-cocoa;
|
||||
modificationTime = 234708321.122562;
|
||||
state = 2;
|
||||
};
|
||||
8EF107CA0DFCF4A100C52EB1 /* AppController.m:62 */ = {
|
||||
isa = PBXFileBreakpoint;
|
||||
actions = (
|
||||
);
|
||||
breakpointStyle = 0;
|
||||
continueAfterActions = 0;
|
||||
countType = 0;
|
||||
delayBeforeContinue = 0;
|
||||
fileReference = 8E01A91E0DE9EED20008BB35 /* AppController.m */;
|
||||
functionName = "mycallback()";
|
||||
hitCount = 0;
|
||||
ignoreCount = 0;
|
||||
lineNumber = 62;
|
||||
location = xcodecapp-cocoa;
|
||||
modificationTime = 234708306.739971;
|
||||
state = 2;
|
||||
};
|
||||
}
|
||||
@@ -7,419 +7,448 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0218442515D32B5D00A782B8 /* pbxprojModifier.py in Resources */ = {isa = PBXBuildFile; fileRef = 0218442415D32B5D00A782B8 /* pbxprojModifier.py */; };
|
||||
022BCEA71468632000B72910 /* project.pbxproj.sample in Resources */ = {isa = PBXBuildFile; fileRef = 022BCEA61468632000B72910 /* project.pbxproj.sample */; };
|
||||
024A041413699DD400DCBE4D /* parser.j in Resources */ = {isa = PBXBuildFile; fileRef = 024A041113699D6800DCBE4D /* parser.j */; };
|
||||
026F3B6213866B0B00EE5B83 /* xcodecapp-icon-inactive.png in Resources */ = {isa = PBXBuildFile; fileRef = 026F3B6113866B0B00EE5B83 /* xcodecapp-icon-inactive.png */; };
|
||||
026F3B6513866E7D00EE5B83 /* xcodecapp-icon-active.png in Resources */ = {isa = PBXBuildFile; fileRef = 026F3B6413866E7D00EE5B83 /* xcodecapp-icon-active.png */; };
|
||||
027D999B13696A7000D3DB13 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; };
|
||||
027D999C13696A7000D3DB13 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
|
||||
027D999E13696A7000D3DB13 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
|
||||
027D999F13696A7000D3DB13 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E01A91E0DE9EED20008BB35 /* AppController.m */; };
|
||||
027D99A113696A7000D3DB13 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
|
||||
027D99A213696A7000D3DB13 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E01A8DD0DE51A7C0008BB35 /* CoreServices.framework */; };
|
||||
027FE49D1462D74C00B1AB92 /* TNXCodeCapp.m in Sources */ = {isa = PBXBuildFile; fileRef = 027FE49C1462D74C00B1AB92 /* TNXCodeCapp.m */; };
|
||||
027FE4BA1462F64E00B1AB92 /* xcodecapp-icon-working.png in Resources */ = {isa = PBXBuildFile; fileRef = 027FE4B91462F64E00B1AB92 /* xcodecapp-icon-working.png */; };
|
||||
027FE4BD1462F7EB00B1AB92 /* FSEventCallback.m in Sources */ = {isa = PBXBuildFile; fileRef = 027FE4BC1462F7EB00B1AB92 /* FSEventCallback.m */; };
|
||||
0289AC9414668CAF003CD975 /* XcodeCapp.icns in Resources */ = {isa = PBXBuildFile; fileRef = 0289AC9314668CAF003CD975 /* XcodeCapp.icns */; };
|
||||
0294F09315D345A500840547 /* mod_pbxproj.py in Resources */ = {isa = PBXBuildFile; fileRef = 0294F09215D345A500840547 /* mod_pbxproj.py */; };
|
||||
02998CEB1369BF7D006C73DB /* Growl Registration Ticket.growlRegDict in Resources */ = {isa = PBXBuildFile; fileRef = 02998CEA1369BF7D006C73DB /* Growl Registration Ticket.growlRegDict */; };
|
||||
02998CF71369C339006C73DB /* Growl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02998CF61369C339006C73DB /* Growl.framework */; };
|
||||
02998CF91369C38A006C73DB /* Growl.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 02998CF61369C339006C73DB /* Growl.framework */; };
|
||||
02B1C8D816B25C74003C6E82 /* TNErrorDataView.m in Sources */ = {isa = PBXBuildFile; fileRef = 02B1C8D716B25C74003C6E82 /* TNErrorDataView.m */; };
|
||||
4E2D0D01154840B400475C01 /* help.rtfd in Resources */ = {isa = PBXBuildFile; fileRef = 4E2D0D00154840B400475C01 /* help.rtfd */; };
|
||||
4E2D0D03154840BC00475C01 /* help.rtfd in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4E2D0D00154840B400475C01 /* help.rtfd */; };
|
||||
651DAE1F13B0CAB900ECA3F0 /* PRHEmptyGrowlDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 651DAE1D13B0CAB900ECA3F0 /* PRHEmptyGrowlDelegate.m */; };
|
||||
E10E4CD717298E5F004AB09E /* project.pbxproj in Resources */ = {isa = PBXBuildFile; fileRef = E164FE2A172188F300263CE3 /* project.pbxproj */; };
|
||||
E164FDD31720E77100263CE3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E164FDD21720E77100263CE3 /* Cocoa.framework */; };
|
||||
E164FDDF1720E77100263CE3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E164FDDE1720E77100263CE3 /* main.m */; };
|
||||
E164FDE91720E77100263CE3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E164FDE71720E77100263CE3 /* MainMenu.xib */; };
|
||||
E164FDF51720EBC100263CE3 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = E164FDF41720EBC100263CE3 /* AppController.m */; };
|
||||
E164FDF71720EBD600263CE3 /* Growl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E164FDF61720EBD600263CE3 /* Growl.framework */; };
|
||||
E164FDFF1720EC5B00263CE3 /* XcodeCapp.m in Sources */ = {isa = PBXBuildFile; fileRef = E164FDFB1720EC5B00263CE3 /* XcodeCapp.m */; };
|
||||
E164FE061720ED4500263CE3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E164FE041720ED4400263CE3 /* InfoPlist.strings */; };
|
||||
E164FE181720F44B00263CE3 /* parser.j in Resources */ = {isa = PBXBuildFile; fileRef = E164FE171720F44B00263CE3 /* parser.j */; };
|
||||
E164FE1A1720F49A00263CE3 /* Growl Registration Ticket.growlRegDict in Resources */ = {isa = PBXBuildFile; fileRef = E164FE191720F49A00263CE3 /* Growl Registration Ticket.growlRegDict */; };
|
||||
E164FE1F1720F51E00263CE3 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E164FE1D1720F50100263CE3 /* CoreServices.framework */; };
|
||||
E164FE211720F55600263CE3 /* Growl.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = E164FDF61720EBD600263CE3 /* Growl.framework */; };
|
||||
E164FE2F1721A34900263CE3 /* icon-active.png in Resources */ = {isa = PBXBuildFile; fileRef = E164FE2C1721A34900263CE3 /* icon-active.png */; };
|
||||
E164FE301721A34900263CE3 /* icon-inactive.png in Resources */ = {isa = PBXBuildFile; fileRef = E164FE2D1721A34900263CE3 /* icon-inactive.png */; };
|
||||
E164FE311721A34900263CE3 /* icon-working.png in Resources */ = {isa = PBXBuildFile; fileRef = E164FE2E1721A34900263CE3 /* icon-working.png */; };
|
||||
E170C3FB17296205003DC489 /* pbxprojModifier.py in CopyFiles */ = {isa = PBXBuildFile; fileRef = E164FE141720F40400263CE3 /* pbxprojModifier.py */; };
|
||||
E170C3FC1729620C003DC489 /* mod_pbxproj.py in CopyFiles */ = {isa = PBXBuildFile; fileRef = E164FE131720F40400263CE3 /* mod_pbxproj.py */; };
|
||||
E170C3FD1729620F003DC489 /* parser.j in CopyFiles */ = {isa = PBXBuildFile; fileRef = E164FE171720F44B00263CE3 /* parser.j */; };
|
||||
E170C40317298833003DC489 /* icon-active@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E170C3FE17298833003DC489 /* icon-active@2x.png */; };
|
||||
E170C40417298833003DC489 /* icon-error.png in Resources */ = {isa = PBXBuildFile; fileRef = E170C3FF17298833003DC489 /* icon-error.png */; };
|
||||
E170C40517298833003DC489 /* icon-error@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E170C40017298833003DC489 /* icon-error@2x.png */; };
|
||||
E170C40617298833003DC489 /* icon-inactive@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E170C40117298833003DC489 /* icon-inactive@2x.png */; };
|
||||
E170C40717298833003DC489 /* icon-working@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E170C40217298833003DC489 /* icon-working@2x.png */; };
|
||||
E17A822D172700B90095CD83 /* XcodeCapp.iconset in Resources */ = {isa = PBXBuildFile; fileRef = E17A822C172700B90095CD83 /* XcodeCapp.iconset */; };
|
||||
E17B55411732F57700809FFB /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E17B55401732F57700809FFB /* Quartz.framework */; };
|
||||
E17B55431732F72700809FFB /* help.pdf in Resources */ = {isa = PBXBuildFile; fileRef = E17B55421732F72700809FFB /* help.pdf */; };
|
||||
E1A5594A172C5EE20088FB61 /* FindSourceFilesOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = E1A55949172C5EE20088FB61 /* FindSourceFilesOperation.m */; };
|
||||
E1A5594D172CA8D40088FB61 /* ProcessSourceOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = E1A5594C172CA8D40088FB61 /* ProcessSourceOperation.m */; };
|
||||
E1E90C411735D846005C6D5B /* DDLogLevel.m in Sources */ = {isa = PBXBuildFile; fileRef = E1E90C391735D846005C6D5B /* DDLogLevel.m */; };
|
||||
E1E90C421735D846005C6D5B /* Notifications.m in Sources */ = {isa = PBXBuildFile; fileRef = E1E90C3C1735D846005C6D5B /* Notifications.m */; };
|
||||
E1E90C431735D846005C6D5B /* UserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = E1E90C3F1735D846005C6D5B /* UserDefaults.m */; };
|
||||
E1E90C4B1735D8A1005C6D5B /* DDASLLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E1E90C461735D8A1005C6D5B /* DDASLLogger.m */; };
|
||||
E1E90C4C1735D8A1005C6D5B /* DDLog.m in Sources */ = {isa = PBXBuildFile; fileRef = E1E90C481735D8A1005C6D5B /* DDLog.m */; };
|
||||
E1E90C4D1735D8A1005C6D5B /* DDTTYLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E1E90C4A1735D8A1005C6D5B /* DDTTYLogger.m */; };
|
||||
E1ED6B71173414E100641628 /* icon_128x128.png in Resources */ = {isa = PBXBuildFile; fileRef = E1ED6B6F173414E100641628 /* icon_128x128.png */; };
|
||||
E1ED6B72173414E100641628 /* icon_128x128@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E1ED6B70173414E100641628 /* icon_128x128@2x.png */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
02998CF81369C383006C73DB /* CopyFiles */ = {
|
||||
E164FE201720F54800263CE3 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
02998CF91369C38A006C73DB /* Growl.framework in CopyFiles */,
|
||||
4E2D0D03154840BC00475C01 /* help.rtfd in CopyFiles */,
|
||||
E164FE211720F55600263CE3 /* Growl.framework in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
E170C3F9172961E6003DC489 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 12;
|
||||
files = (
|
||||
E170C3FB17296205003DC489 /* pbxprojModifier.py in CopyFiles */,
|
||||
E170C3FC1729620C003DC489 /* mod_pbxproj.py in CopyFiles */,
|
||||
E170C3FD1729620F003DC489 /* parser.j in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0218442415D32B5D00A782B8 /* pbxprojModifier.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = pbxprojModifier.py; sourceTree = "<group>"; };
|
||||
022BCEA61468632000B72910 /* project.pbxproj.sample */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = project.pbxproj.sample; sourceTree = "<group>"; };
|
||||
024A041113699D6800DCBE4D /* parser.j */ = {isa = PBXFileReference; explicitFileType = sourcecode.javascript; fileEncoding = 4; path = parser.j; sourceTree = "<group>"; };
|
||||
026F3B6113866B0B00EE5B83 /* xcodecapp-icon-inactive.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "xcodecapp-icon-inactive.png"; sourceTree = "<group>"; };
|
||||
026F3B6413866E7D00EE5B83 /* xcodecapp-icon-active.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "xcodecapp-icon-active.png"; sourceTree = "<group>"; };
|
||||
027D9989136969DD00D3DB13 /* XcodeCapp.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XcodeCapp.pch; sourceTree = "<group>"; };
|
||||
027D99A613696A7000D3DB13 /* XcodeCapp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XcodeCapp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
027FE49B1462D74C00B1AB92 /* TNXCodeCapp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TNXCodeCapp.h; sourceTree = "<group>"; };
|
||||
027FE49C1462D74C00B1AB92 /* TNXCodeCapp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TNXCodeCapp.m; sourceTree = "<group>"; };
|
||||
027FE4B91462F64E00B1AB92 /* xcodecapp-icon-working.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "xcodecapp-icon-working.png"; sourceTree = "<group>"; };
|
||||
027FE4BB1462F7D600B1AB92 /* FSEventCallback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSEventCallback.h; sourceTree = "<group>"; };
|
||||
027FE4BC1462F7EB00B1AB92 /* FSEventCallback.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSEventCallback.m; sourceTree = "<group>"; };
|
||||
0289AC9314668CAF003CD975 /* XcodeCapp.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = XcodeCapp.icns; sourceTree = "<group>"; };
|
||||
0294F09215D345A500840547 /* mod_pbxproj.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = mod_pbxproj.py; sourceTree = "<group>"; };
|
||||
02998CEA1369BF7D006C73DB /* Growl Registration Ticket.growlRegDict */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "Growl Registration Ticket.growlRegDict"; sourceTree = "<group>"; };
|
||||
02998CF61369C339006C73DB /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Growl.framework; sourceTree = "<group>"; };
|
||||
02B1C8D616B25C74003C6E82 /* TNErrorDataView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TNErrorDataView.h; sourceTree = "<group>"; };
|
||||
02B1C8D716B25C74003C6E82 /* TNErrorDataView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TNErrorDataView.m; sourceTree = "<group>"; };
|
||||
089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
|
||||
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
|
||||
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = "<group>"; };
|
||||
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
|
||||
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
|
||||
4E2D0D00154840B400475C01 /* help.rtfd */ = {isa = PBXFileReference; lastKnownFileType = wrapper.rtfd; path = help.rtfd; sourceTree = "<group>"; };
|
||||
651DAE1C13B0CAB900ECA3F0 /* PRHEmptyGrowlDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.objj.h; path = PRHEmptyGrowlDelegate.h; sourceTree = "<group>"; };
|
||||
651DAE1D13B0CAB900ECA3F0 /* PRHEmptyGrowlDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PRHEmptyGrowlDelegate.m; sourceTree = "<group>"; };
|
||||
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
8E01A8DD0DE51A7C0008BB35 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = /System/Library/Frameworks/CoreServices.framework; sourceTree = "<absolute>"; };
|
||||
8E01A91D0DE9EED20008BB35 /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = "<group>"; };
|
||||
8E01A91E0DE9EED20008BB35 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AppController.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
E12AA95F14637828006F55D6 /* macros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.objj.h; path = macros.h; sourceTree = "<group>"; };
|
||||
E164FDCF1720E77100263CE3 /* XcodeCapp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XcodeCapp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E164FDD21720E77100263CE3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
E164FDD51720E77100263CE3 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
|
||||
E164FDD61720E77100263CE3 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
|
||||
E164FDD71720E77100263CE3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
E164FDDA1720E77100263CE3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
E164FDDE1720E77100263CE3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
E164FDE01720E77100263CE3 /* XcodeCapp-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XcodeCapp-Prefix.pch"; sourceTree = "<group>"; };
|
||||
E164FDE81720E77100263CE3 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
E164FDEF1720E7FA00263CE3 /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
E164FDF01720E7FA00263CE3 /* Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
E164FDF41720EBC100263CE3 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AppController.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
E164FDF61720EBD600263CE3 /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = XcodeCapp/Growl.framework; sourceTree = SOURCE_ROOT; };
|
||||
E164FDFB1720EC5B00263CE3 /* XcodeCapp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = XcodeCapp.m; sourceTree = "<group>"; usesTabs = 0; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
E164FE051720ED4500263CE3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = XcodeCapp/en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
E164FE131720F40400263CE3 /* mod_pbxproj.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = mod_pbxproj.py; sourceTree = "<group>"; };
|
||||
E164FE141720F40400263CE3 /* pbxprojModifier.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = pbxprojModifier.py; sourceTree = "<group>"; };
|
||||
E164FE171720F44B00263CE3 /* parser.j */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = parser.j; sourceTree = "<group>"; };
|
||||
E164FE191720F49A00263CE3 /* Growl Registration Ticket.growlRegDict */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "Growl Registration Ticket.growlRegDict"; sourceTree = "<group>"; };
|
||||
E164FE1D1720F50100263CE3 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
|
||||
E164FE2A172188F300263CE3 /* project.pbxproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.pbxproject; lineEnding = 0; name = project.pbxproj; path = Resources/project.pbxproj; sourceTree = "<group>"; };
|
||||
E164FE2C1721A34900263CE3 /* icon-active.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon-active.png"; path = "Resources/icon-active.png"; sourceTree = "<group>"; };
|
||||
E164FE2D1721A34900263CE3 /* icon-inactive.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon-inactive.png"; path = "Resources/icon-inactive.png"; sourceTree = "<group>"; };
|
||||
E164FE2E1721A34900263CE3 /* icon-working.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon-working.png"; path = "Resources/icon-working.png"; sourceTree = "<group>"; };
|
||||
E170C3FE17298833003DC489 /* icon-active@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon-active@2x.png"; path = "Resources/icon-active@2x.png"; sourceTree = "<group>"; };
|
||||
E170C3FF17298833003DC489 /* icon-error.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon-error.png"; path = "Resources/icon-error.png"; sourceTree = "<group>"; };
|
||||
E170C40017298833003DC489 /* icon-error@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon-error@2x.png"; path = "Resources/icon-error@2x.png"; sourceTree = "<group>"; };
|
||||
E170C40117298833003DC489 /* icon-inactive@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon-inactive@2x.png"; path = "Resources/icon-inactive@2x.png"; sourceTree = "<group>"; };
|
||||
E170C40217298833003DC489 /* icon-working@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon-working@2x.png"; path = "Resources/icon-working@2x.png"; sourceTree = "<group>"; };
|
||||
E17A822C172700B90095CD83 /* XcodeCapp.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = XcodeCapp.iconset; path = Resources/XcodeCapp.iconset; sourceTree = "<group>"; };
|
||||
E17B55401732F57700809FFB /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; };
|
||||
E17B55421732F72700809FFB /* help.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = help.pdf; path = Resources/help.pdf; sourceTree = "<group>"; };
|
||||
E1A55949172C5EE20088FB61 /* FindSourceFilesOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = FindSourceFilesOperation.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
E1A5594C172CA8D40088FB61 /* ProcessSourceOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ProcessSourceOperation.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
E1E90C371735D82B005C6D5B /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = "<group>"; };
|
||||
E1E90C381735D846005C6D5B /* DDLogLevel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDLogLevel.h; sourceTree = "<group>"; };
|
||||
E1E90C391735D846005C6D5B /* DDLogLevel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDLogLevel.m; sourceTree = "<group>"; };
|
||||
E1E90C3A1735D846005C6D5B /* FindSourceFilesOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FindSourceFilesOperation.h; sourceTree = "<group>"; };
|
||||
E1E90C3B1735D846005C6D5B /* Notifications.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Notifications.h; sourceTree = "<group>"; };
|
||||
E1E90C3C1735D846005C6D5B /* Notifications.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Notifications.m; sourceTree = "<group>"; };
|
||||
E1E90C3D1735D846005C6D5B /* ProcessSourceOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProcessSourceOperation.h; sourceTree = "<group>"; };
|
||||
E1E90C3E1735D846005C6D5B /* UserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserDefaults.h; sourceTree = "<group>"; };
|
||||
E1E90C3F1735D846005C6D5B /* UserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserDefaults.m; sourceTree = "<group>"; };
|
||||
E1E90C401735D846005C6D5B /* XcodeCapp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XcodeCapp.h; sourceTree = "<group>"; };
|
||||
E1E90C451735D8A1005C6D5B /* DDASLLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDASLLogger.h; sourceTree = "<group>"; };
|
||||
E1E90C461735D8A1005C6D5B /* DDASLLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDASLLogger.m; sourceTree = "<group>"; };
|
||||
E1E90C471735D8A1005C6D5B /* DDLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDLog.h; sourceTree = "<group>"; };
|
||||
E1E90C481735D8A1005C6D5B /* DDLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDLog.m; sourceTree = "<group>"; };
|
||||
E1E90C491735D8A1005C6D5B /* DDTTYLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDTTYLogger.h; sourceTree = "<group>"; };
|
||||
E1E90C4A1735D8A1005C6D5B /* DDTTYLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDTTYLogger.m; sourceTree = "<group>"; };
|
||||
E1ED6B6F173414E100641628 /* icon_128x128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = icon_128x128.png; path = Resources/icon_128x128.png; sourceTree = "<group>"; };
|
||||
E1ED6B70173414E100641628 /* icon_128x128@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon_128x128@2x.png"; path = "Resources/icon_128x128@2x.png"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
027D99A013696A7000D3DB13 /* Frameworks */ = {
|
||||
E164FDCC1720E77100263CE3 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
027D99A113696A7000D3DB13 /* Cocoa.framework in Frameworks */,
|
||||
027D99A213696A7000D3DB13 /* CoreServices.framework in Frameworks */,
|
||||
02998CF71369C339006C73DB /* Growl.framework in Frameworks */,
|
||||
E164FDD31720E77100263CE3 /* Cocoa.framework in Frameworks */,
|
||||
E164FE1F1720F51E00263CE3 /* CoreServices.framework in Frameworks */,
|
||||
E17B55411732F57700809FFB /* Quartz.framework in Frameworks */,
|
||||
E164FDF71720EBD600263CE3 /* Growl.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
080E96DDFE201D6D7F000001 /* Classes */ = {
|
||||
E164FDC61720E77100263CE3 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
651DAE1C13B0CAB900ECA3F0 /* PRHEmptyGrowlDelegate.h */,
|
||||
651DAE1D13B0CAB900ECA3F0 /* PRHEmptyGrowlDelegate.m */,
|
||||
02B1C8D616B25C74003C6E82 /* TNErrorDataView.h */,
|
||||
02B1C8D716B25C74003C6E82 /* TNErrorDataView.m */,
|
||||
8E01A91D0DE9EED20008BB35 /* AppController.h */,
|
||||
8E01A91E0DE9EED20008BB35 /* AppController.m */,
|
||||
027FE49B1462D74C00B1AB92 /* TNXCodeCapp.h */,
|
||||
027FE49C1462D74C00B1AB92 /* TNXCodeCapp.m */,
|
||||
027FE4BB1462F7D600B1AB92 /* FSEventCallback.h */,
|
||||
027FE4BC1462F7EB00B1AB92 /* FSEventCallback.m */,
|
||||
E164FDD81720E77100263CE3 /* XcodeCapp */,
|
||||
E164FDD11720E77100263CE3 /* Frameworks */,
|
||||
E164FDD01720E77100263CE3 /* Products */,
|
||||
);
|
||||
name = Classes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
|
||||
E164FDD01720E77100263CE3 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8E01A8DD0DE51A7C0008BB35 /* CoreServices.framework */,
|
||||
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
|
||||
);
|
||||
name = "Linked Frameworks";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
29B97324FDCFA39411CA2CEA /* AppKit.framework */,
|
||||
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */,
|
||||
29B97325FDCFA39411CA2CEA /* Foundation.framework */,
|
||||
);
|
||||
name = "Other Frameworks";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
19C28FACFE9D520D11CA2CBB /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
027D99A613696A7000D3DB13 /* XcodeCapp.app */,
|
||||
E164FDCF1720E77100263CE3 /* XcodeCapp.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97314FDCFA39411CA2CEA /* xcodecapp-cocoa */ = {
|
||||
E164FDD11720E77100263CE3 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
080E96DDFE201D6D7F000001 /* Classes */,
|
||||
29B97315FDCFA39411CA2CEA /* Other Sources */,
|
||||
29B97317FDCFA39411CA2CEA /* Resources */,
|
||||
29B97323FDCFA39411CA2CEA /* Frameworks */,
|
||||
19C28FACFE9D520D11CA2CBB /* Products */,
|
||||
E164FDD21720E77100263CE3 /* Cocoa.framework */,
|
||||
E164FE1D1720F50100263CE3 /* CoreServices.framework */,
|
||||
E164FDF61720EBD600263CE3 /* Growl.framework */,
|
||||
E164FDD41720E77100263CE3 /* Other Frameworks */,
|
||||
);
|
||||
name = "xcodecapp-cocoa";
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97315FDCFA39411CA2CEA /* Other Sources */ = {
|
||||
E164FDD41720E77100263CE3 /* Other Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
027D9989136969DD00D3DB13 /* XcodeCapp.pch */,
|
||||
29B97316FDCFA39411CA2CEA /* main.m */,
|
||||
E12AA95F14637828006F55D6 /* macros.h */,
|
||||
E164FDD51720E77100263CE3 /* AppKit.framework */,
|
||||
E164FDD61720E77100263CE3 /* CoreData.framework */,
|
||||
E164FDD71720E77100263CE3 /* Foundation.framework */,
|
||||
E17B55401732F57700809FFB /* Quartz.framework */,
|
||||
);
|
||||
name = "Other Sources";
|
||||
name = "Other Frameworks";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97317FDCFA39411CA2CEA /* Resources */ = {
|
||||
E164FDD81720E77100263CE3 /* XcodeCapp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0294F09215D345A500840547 /* mod_pbxproj.py */,
|
||||
0218442415D32B5D00A782B8 /* pbxprojModifier.py */,
|
||||
4E2D0D00154840B400475C01 /* help.rtfd */,
|
||||
022BCEA61468632000B72910 /* project.pbxproj.sample */,
|
||||
027FE4B91462F64E00B1AB92 /* xcodecapp-icon-working.png */,
|
||||
026F3B6413866E7D00EE5B83 /* xcodecapp-icon-active.png */,
|
||||
026F3B6113866B0B00EE5B83 /* xcodecapp-icon-inactive.png */,
|
||||
024A041113699D6800DCBE4D /* parser.j */,
|
||||
8D1107310486CEB800E47090 /* Info.plist */,
|
||||
0289AC9314668CAF003CD975 /* XcodeCapp.icns */,
|
||||
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
|
||||
29B97318FDCFA39411CA2CEA /* MainMenu.nib */,
|
||||
02998CEA1369BF7D006C73DB /* Growl Registration Ticket.growlRegDict */,
|
||||
E164FDF41720EBC100263CE3 /* AppController.m */,
|
||||
E1E90C371735D82B005C6D5B /* AppController.h */,
|
||||
E164FDFB1720EC5B00263CE3 /* XcodeCapp.m */,
|
||||
E1E90C401735D846005C6D5B /* XcodeCapp.h */,
|
||||
E1E90C3C1735D846005C6D5B /* Notifications.m */,
|
||||
E1E90C3B1735D846005C6D5B /* Notifications.h */,
|
||||
E1E90C3F1735D846005C6D5B /* UserDefaults.m */,
|
||||
E1E90C3E1735D846005C6D5B /* UserDefaults.h */,
|
||||
E1E90C391735D846005C6D5B /* DDLogLevel.m */,
|
||||
E1E90C381735D846005C6D5B /* DDLogLevel.h */,
|
||||
E1A55947172C5EA80088FB61 /* Operations */,
|
||||
E1E90C441735D8A1005C6D5B /* Lumberjack */,
|
||||
E164FE121720F3E100263CE3 /* Scripts */,
|
||||
E164FE071720F25400263CE3 /* Resources */,
|
||||
E164FDD91720E77100263CE3 /* Supporting Files */,
|
||||
);
|
||||
path = XcodeCapp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E164FDD91720E77100263CE3 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E164FDDA1720E77100263CE3 /* Info.plist */,
|
||||
E164FE041720ED4400263CE3 /* InfoPlist.strings */,
|
||||
E164FDDE1720E77100263CE3 /* main.m */,
|
||||
E164FDE01720E77100263CE3 /* XcodeCapp-Prefix.pch */,
|
||||
E164FDEF1720E7FA00263CE3 /* Debug.xcconfig */,
|
||||
E164FDF01720E7FA00263CE3 /* Release.xcconfig */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E164FE071720F25400263CE3 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E1ED6B6F173414E100641628 /* icon_128x128.png */,
|
||||
E1ED6B70173414E100641628 /* icon_128x128@2x.png */,
|
||||
E164FDE71720E77100263CE3 /* MainMenu.xib */,
|
||||
E164FE2D1721A34900263CE3 /* icon-inactive.png */,
|
||||
E170C40117298833003DC489 /* icon-inactive@2x.png */,
|
||||
E164FE2E1721A34900263CE3 /* icon-working.png */,
|
||||
E170C40217298833003DC489 /* icon-working@2x.png */,
|
||||
E164FE2C1721A34900263CE3 /* icon-active.png */,
|
||||
E170C3FE17298833003DC489 /* icon-active@2x.png */,
|
||||
E170C3FF17298833003DC489 /* icon-error.png */,
|
||||
E170C40017298833003DC489 /* icon-error@2x.png */,
|
||||
E17A822C172700B90095CD83 /* XcodeCapp.iconset */,
|
||||
E17B55421732F72700809FFB /* help.pdf */,
|
||||
E164FE2A172188F300263CE3 /* project.pbxproj */,
|
||||
E164FE191720F49A00263CE3 /* Growl Registration Ticket.growlRegDict */,
|
||||
);
|
||||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
|
||||
E164FE121720F3E100263CE3 /* Scripts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
02998CF61369C339006C73DB /* Growl.framework */,
|
||||
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
|
||||
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
|
||||
E164FE171720F44B00263CE3 /* parser.j */,
|
||||
E164FE141720F40400263CE3 /* pbxprojModifier.py */,
|
||||
E164FE131720F40400263CE3 /* mod_pbxproj.py */,
|
||||
);
|
||||
name = Frameworks;
|
||||
path = Scripts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E1A55947172C5EA80088FB61 /* Operations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E1A55949172C5EE20088FB61 /* FindSourceFilesOperation.m */,
|
||||
E1E90C3A1735D846005C6D5B /* FindSourceFilesOperation.h */,
|
||||
E1A5594C172CA8D40088FB61 /* ProcessSourceOperation.m */,
|
||||
E1E90C3D1735D846005C6D5B /* ProcessSourceOperation.h */,
|
||||
);
|
||||
name = Operations;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E1E90C441735D8A1005C6D5B /* Lumberjack */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E1E90C451735D8A1005C6D5B /* DDASLLogger.h */,
|
||||
E1E90C461735D8A1005C6D5B /* DDASLLogger.m */,
|
||||
E1E90C471735D8A1005C6D5B /* DDLog.h */,
|
||||
E1E90C481735D8A1005C6D5B /* DDLog.m */,
|
||||
E1E90C491735D8A1005C6D5B /* DDTTYLogger.h */,
|
||||
E1E90C4A1735D8A1005C6D5B /* DDTTYLogger.m */,
|
||||
);
|
||||
path = Lumberjack;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
027D999913696A7000D3DB13 /* XcodeCapp */ = {
|
||||
E164FDCE1720E77100263CE3 /* XcodeCapp */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 027D99A313696A7000D3DB13 /* Build configuration list for PBXNativeTarget "XcodeCapp" */;
|
||||
buildConfigurationList = E164FDEC1720E77100263CE3 /* Build configuration list for PBXNativeTarget "XcodeCapp" */;
|
||||
buildPhases = (
|
||||
024699001602C04A00F0AE43 /* ShellScript */,
|
||||
027D999A13696A7000D3DB13 /* Resources */,
|
||||
027D999D13696A7000D3DB13 /* Sources */,
|
||||
02998CF81369C383006C73DB /* CopyFiles */,
|
||||
027D99A013696A7000D3DB13 /* Frameworks */,
|
||||
E164FDCB1720E77100263CE3 /* Sources */,
|
||||
E164FDCC1720E77100263CE3 /* Frameworks */,
|
||||
E164FDCD1720E77100263CE3 /* Resources */,
|
||||
E164FE201720F54800263CE3 /* CopyFiles */,
|
||||
E170C3F9172961E6003DC489 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = XcodeCapp;
|
||||
productInstallPath = "$(HOME)/Applications";
|
||||
productName = "xcodecapp-cocoa";
|
||||
productReference = 027D99A613696A7000D3DB13 /* XcodeCapp.app */;
|
||||
productName = XcodeCapp;
|
||||
productReference = E164FDCF1720E77100263CE3 /* XcodeCapp.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
29B97313FDCFA39411CA2CEA /* Project object */ = {
|
||||
E164FDC71720E77100263CE3 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0440;
|
||||
LastUpgradeCheck = 0460;
|
||||
ORGANIZATIONNAME = "Cappuccino Project";
|
||||
};
|
||||
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "XcodeCapp" */;
|
||||
buildConfigurationList = E164FDCA1720E77100263CE3 /* Build configuration list for PBXProject "XcodeCapp" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 1;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 29B97314FDCFA39411CA2CEA /* xcodecapp-cocoa */;
|
||||
mainGroup = E164FDC61720E77100263CE3;
|
||||
productRefGroup = E164FDD01720E77100263CE3 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
027D999913696A7000D3DB13 /* XcodeCapp */,
|
||||
E164FDCE1720E77100263CE3 /* XcodeCapp */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
027D999A13696A7000D3DB13 /* Resources */ = {
|
||||
E164FDCD1720E77100263CE3 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
027D999B13696A7000D3DB13 /* MainMenu.nib in Resources */,
|
||||
027D999C13696A7000D3DB13 /* InfoPlist.strings in Resources */,
|
||||
024A041413699DD400DCBE4D /* parser.j in Resources */,
|
||||
02998CEB1369BF7D006C73DB /* Growl Registration Ticket.growlRegDict in Resources */,
|
||||
026F3B6213866B0B00EE5B83 /* xcodecapp-icon-inactive.png in Resources */,
|
||||
026F3B6513866E7D00EE5B83 /* xcodecapp-icon-active.png in Resources */,
|
||||
027FE4BA1462F64E00B1AB92 /* xcodecapp-icon-working.png in Resources */,
|
||||
0289AC9414668CAF003CD975 /* XcodeCapp.icns in Resources */,
|
||||
022BCEA71468632000B72910 /* project.pbxproj.sample in Resources */,
|
||||
4E2D0D01154840B400475C01 /* help.rtfd in Resources */,
|
||||
0218442515D32B5D00A782B8 /* pbxprojModifier.py in Resources */,
|
||||
0294F09315D345A500840547 /* mod_pbxproj.py in Resources */,
|
||||
E164FDE91720E77100263CE3 /* MainMenu.xib in Resources */,
|
||||
E164FE061720ED4500263CE3 /* InfoPlist.strings in Resources */,
|
||||
E10E4CD717298E5F004AB09E /* project.pbxproj in Resources */,
|
||||
E164FE181720F44B00263CE3 /* parser.j in Resources */,
|
||||
E164FE1A1720F49A00263CE3 /* Growl Registration Ticket.growlRegDict in Resources */,
|
||||
E164FE2F1721A34900263CE3 /* icon-active.png in Resources */,
|
||||
E164FE301721A34900263CE3 /* icon-inactive.png in Resources */,
|
||||
E164FE311721A34900263CE3 /* icon-working.png in Resources */,
|
||||
E17A822D172700B90095CD83 /* XcodeCapp.iconset in Resources */,
|
||||
E170C40317298833003DC489 /* icon-active@2x.png in Resources */,
|
||||
E170C40417298833003DC489 /* icon-error.png in Resources */,
|
||||
E170C40517298833003DC489 /* icon-error@2x.png in Resources */,
|
||||
E170C40617298833003DC489 /* icon-inactive@2x.png in Resources */,
|
||||
E170C40717298833003DC489 /* icon-working@2x.png in Resources */,
|
||||
E17B55431732F72700809FFB /* help.pdf in Resources */,
|
||||
E1ED6B71173414E100641628 /* icon_128x128.png in Resources */,
|
||||
E1ED6B72173414E100641628 /* icon_128x128@2x.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
024699001602C04A00F0AE43 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(SRCROOT)/*.m",
|
||||
"$(SRCROOT)/*.h",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /usr/bin/perl;
|
||||
shellScript = "#!/usr/bin/perl\n\nwhile (<>) {\n s/\\s+$//;\n print \"$_\\n\";\n}";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
027D999D13696A7000D3DB13 /* Sources */ = {
|
||||
E164FDCB1720E77100263CE3 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
027D999E13696A7000D3DB13 /* main.m in Sources */,
|
||||
027D999F13696A7000D3DB13 /* AppController.m in Sources */,
|
||||
651DAE1F13B0CAB900ECA3F0 /* PRHEmptyGrowlDelegate.m in Sources */,
|
||||
027FE49D1462D74C00B1AB92 /* TNXCodeCapp.m in Sources */,
|
||||
027FE4BD1462F7EB00B1AB92 /* FSEventCallback.m in Sources */,
|
||||
02B1C8D816B25C74003C6E82 /* TNErrorDataView.m in Sources */,
|
||||
E164FDDF1720E77100263CE3 /* main.m in Sources */,
|
||||
E164FDF51720EBC100263CE3 /* AppController.m in Sources */,
|
||||
E164FDFF1720EC5B00263CE3 /* XcodeCapp.m in Sources */,
|
||||
E1A5594A172C5EE20088FB61 /* FindSourceFilesOperation.m in Sources */,
|
||||
E1A5594D172CA8D40088FB61 /* ProcessSourceOperation.m in Sources */,
|
||||
E1E90C411735D846005C6D5B /* DDLogLevel.m in Sources */,
|
||||
E1E90C421735D846005C6D5B /* Notifications.m in Sources */,
|
||||
E1E90C431735D846005C6D5B /* UserDefaults.m in Sources */,
|
||||
E1E90C4B1735D8A1005C6D5B /* DDASLLogger.m in Sources */,
|
||||
E1E90C4C1735D8A1005C6D5B /* DDLog.m in Sources */,
|
||||
E1E90C4D1735D8A1005C6D5B /* DDTTYLogger.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
|
||||
E164FDE71720E77100263CE3 /* MainMenu.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
089C165DFE840E0CC02AAC07 /* English */,
|
||||
E164FDE81720E77100263CE3 /* en */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
name = MainMenu.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = {
|
||||
E164FE041720ED4400263CE3 /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
29B97319FDCFA39411CA2CEA /* English */,
|
||||
E164FE051720ED4500263CE3 /* en */,
|
||||
);
|
||||
name = MainMenu.nib;
|
||||
name = InfoPlist.strings;
|
||||
path = ..;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
027D99A413696A7000D3DB13 /* Debug */ = {
|
||||
E164FDEA1720E77100263CE3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = E164FDEF1720E7FA00263CE3 /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)\"",
|
||||
);
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_GC = unsupported;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = XcodeCapp.pch;
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = /Applications;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.6.8;
|
||||
PRODUCT_NAME = XcodeCapp;
|
||||
DSTROOT = /;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
027D99A513696A7000D3DB13 /* Release */ = {
|
||||
E164FDEB1720E77100263CE3 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = E164FDF01720E7FA00263CE3 /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)\"",
|
||||
);
|
||||
GCC_ENABLE_OBJC_GC = unsupported;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = XcodeCapp.pch;
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = /Applications;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.6.8;
|
||||
PRODUCT_NAME = XcodeCapp;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEPLOYMENT_LOCATION = YES;
|
||||
DSTROOT = /;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
C01FCF4F08A954540054247B /* Debug */ = {
|
||||
E164FDED1720E77100263CE3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
|
||||
FRAMEWORK_SEARCH_PATHS = "";
|
||||
GCC_ENABLE_OBJC_GC = required;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
|
||||
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = "";
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.5;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/XcodeCapp\"",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.6.8;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
C01FCF5008A954540054247B /* Release */ = {
|
||||
E164FDEE1720E77100263CE3 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
|
||||
FRAMEWORK_SEARCH_PATHS = "";
|
||||
GCC_ENABLE_OBJC_GC = required;
|
||||
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = "";
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.5;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/XcodeCapp\"",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.6.8;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
027D99A313696A7000D3DB13 /* Build configuration list for PBXNativeTarget "XcodeCapp" */ = {
|
||||
E164FDCA1720E77100263CE3 /* Build configuration list for PBXProject "XcodeCapp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
027D99A413696A7000D3DB13 /* Debug */,
|
||||
027D99A513696A7000D3DB13 /* Release */,
|
||||
E164FDEA1720E77100263CE3 /* Debug */,
|
||||
E164FDEB1720E77100263CE3 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "XcodeCapp" */ = {
|
||||
E164FDEC1720E77100263CE3 /* Build configuration list for PBXNativeTarget "XcodeCapp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
C01FCF4F08A954540054247B /* Debug */,
|
||||
C01FCF5008A954540054247B /* Release */,
|
||||
E164FDED1720E77100263CE3 /* Debug */,
|
||||
E164FDEE1720E77100263CE3 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
|
||||
rootObject = E164FDC71720E77100263CE3 /* Project object */;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEWorkspaceUserSettings_HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key>
|
||||
<true/>
|
||||
<key>IDEWorkspaceUserSettings_SnapshotAutomaticallyBeforeSignificantChanges</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Bucket
|
||||
type = "1"
|
||||
version = "1.0">
|
||||
</Bucket>
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>XcodeCapp Debug.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>XcodeCapp Release.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>XcodeCapp.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>027D998A13696A0700D3DB13</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>027D999913696A7000D3DB13</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>8D1107260486CEB800E47090</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2011 Antoine Mercadal (<primalmotion@archipelproject.org>)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <CoreServices/CoreServices.h>
|
||||
#import <Quartz/Quartz.h>
|
||||
|
||||
@class XcodeCapp;
|
||||
|
||||
@interface AppController : NSObject <NSApplicationDelegate, NSMenuDelegate>
|
||||
|
||||
@property (strong) IBOutlet NSMenu *statusMenu;
|
||||
@property (assign) IBOutlet NSMenuItem *menuItemHistory;
|
||||
@property (assign) IBOutlet NSMenuItem *menuItemOpenInXcode;
|
||||
@property (assign) IBOutlet NSMenuItem *menuItemListen;
|
||||
|
||||
@property (strong) IBOutlet NSPanel *aboutWindow;
|
||||
@property (strong) IBOutlet NSWindow *preferencesWindow;
|
||||
|
||||
@property (strong) IBOutlet NSWindow *helpWindow;
|
||||
@property (assign) IBOutlet PDFView *helpView;
|
||||
|
||||
@property (strong) IBOutlet NSUserDefaultsController *preferencesController;
|
||||
@property (strong) IBOutlet XcodeCapp *xcc;
|
||||
|
||||
+ (AppController *)sharedAppController;
|
||||
|
||||
- (IBAction)loadProject:(id)aSender;
|
||||
- (IBAction)openHelp:(id)aSender;
|
||||
- (IBAction)openAbout:(id)aSender;
|
||||
|
||||
- (void)loadProjectAtPath:(NSString *)path;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2011 Antoine Mercadal (<primalmotion@archipelproject.org>)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#import <AppKit/NSApplication.h>
|
||||
|
||||
#import "AppController.h"
|
||||
#import "Notifications.h"
|
||||
#import "XcodeCapp.h"
|
||||
#import "UserDefaults.h"
|
||||
|
||||
|
||||
AppController *SharedAppControllerInstance = nil;
|
||||
|
||||
|
||||
@interface AppController ()
|
||||
|
||||
@property (nonatomic) NSImage *iconActive;
|
||||
@property (nonatomic) NSImage *iconInactive;
|
||||
@property (nonatomic) NSImage *iconWorking;
|
||||
@property (nonatomic) NSImage *iconError;
|
||||
@property (nonatomic) NSMenu *recentMenu;
|
||||
@property NSStatusItem *statusItem;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation AppController
|
||||
|
||||
+ (AppController *)sharedAppController
|
||||
{
|
||||
return SharedAppControllerInstance;
|
||||
}
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
SharedAppControllerInstance = self;
|
||||
|
||||
[self registerDefaultPreferences];
|
||||
[self initLogging];
|
||||
|
||||
DDLogVerbose(@"\n******************************\n** XcodeCapp started **\n******************************\n");
|
||||
|
||||
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
|
||||
self.statusItem.menu = self.statusMenu;
|
||||
self.statusItem.image = self.iconInactive;
|
||||
self.statusItem.highlightMode = YES;
|
||||
self.statusItem.length = self.iconInactive.size.width + 12; // Add some space around the icon
|
||||
self.statusMenu.delegate = self;
|
||||
|
||||
self.aboutWindow.backgroundColor = [NSColor whiteColor];
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
double firstLaunchVersion = [defaults doubleForKey:kDefaultFirstLaunchVersion];
|
||||
|
||||
// Note: the scanner will only get the major.minor version numbers, which is what we want.
|
||||
NSScanner *scanner = [NSScanner scannerWithString:[self bundleVersion]];
|
||||
double appVersion = 0.0;
|
||||
[scanner scanDouble:&appVersion];
|
||||
|
||||
if ([defaults boolForKey:kDefaultFirstLaunch] || appVersion > firstLaunchVersion)
|
||||
{
|
||||
[defaults setBool:NO forKey:kDefaultFirstLaunch];
|
||||
[defaults setDouble:appVersion forKey:kDefaultFirstLaunchVersion];
|
||||
[self openHelp:self];
|
||||
}
|
||||
|
||||
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
|
||||
|
||||
[defaultCenter addObserver:self selector:@selector(batchDidStart:) name:XCCBatchDidStartNotification object:nil];
|
||||
[defaultCenter addObserver:self selector:@selector(batchDidEnd:) name:XCCBatchDidEndNotification object:nil];
|
||||
[defaultCenter addObserver:self selector:@selector(projectDidFinishLoading:) name:XCCProjectDidFinishLoadingNotification object:nil];
|
||||
|
||||
[self pruneProjectHistory];
|
||||
[self updateHistoryMenu];
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification
|
||||
{
|
||||
if (![self.xcc executablesAreAccessible])
|
||||
{
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
|
||||
NSRunAlertPanel(@"Executables are missing.", @"Please make sure that python, jsc, objj and nib2cib (or symlinks to them) are somewhere within one of these directories:\n\n/bin\n/usr/bin\n/usr/local/bin\n/usr/local/narwhal/bin\n~/bin\n\nThen launch XcodeCapp again.", @"Quit", nil, nil);
|
||||
|
||||
[[NSApplication sharedApplication] terminate:self];
|
||||
return;
|
||||
}
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
if (![defaults boolForKey:kDefaultXCCReopenLastProject])
|
||||
return;
|
||||
|
||||
NSString *lastOpenedPath = [defaults objectForKey:kDefaultLastOpenedPath];
|
||||
|
||||
if (lastOpenedPath)
|
||||
{
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:lastOpenedPath])
|
||||
[self loadProjectAtPath:lastOpenedPath];
|
||||
else
|
||||
[defaults removeObjectForKey:kDefaultLastOpenedPath];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Register default values for preferences
|
||||
*/
|
||||
- (void)registerDefaultPreferences
|
||||
{
|
||||
NSDictionary *appDefaults = @{
|
||||
kDefaultLastEventId: [NSNumber numberWithUnsignedLongLong:kFSEventStreamEventIdSinceNow],
|
||||
kDefaultFirstLaunch: @YES,
|
||||
kDefaultFirstLaunchVersion: @2.0,
|
||||
kDefaultXCCAPIMode: [NSNumber numberWithInt:kXCCAPIModeAuto],
|
||||
kDefaultXCCReactToInodeMod: @YES,
|
||||
kDefaultXCCReopenLastProject: @YES,
|
||||
kDefaultXCCAutoOpenErrorsPanelOnWarnings: @YES,
|
||||
kDefaultXCCAutoOpenErrorsPanelOnErrors: @YES,
|
||||
kDefaultXCCProjectHistory: [NSArray new],
|
||||
kDefaultMaxRecentProjects: @20,
|
||||
kDefaultLogLevel: [NSNumber numberWithInt:LOG_LEVEL_WARN],
|
||||
kDefaultAutoOpenXcodeProject: @YES,
|
||||
kDefaultShowProcessingNotices: @YES
|
||||
};
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
[defaults registerDefaults:appDefaults];
|
||||
[defaults synchronize];
|
||||
|
||||
[defaults addObserver:self
|
||||
forKeyPath:kDefaultMaxRecentProjects
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
}
|
||||
|
||||
- (void)initLogging
|
||||
{
|
||||
#if DEBUG
|
||||
[DDLog addLogger:[DDTTYLogger sharedInstance]];
|
||||
[[DDTTYLogger sharedInstance] setColorsEnabled:YES];
|
||||
[DDLogLevel setLogLevel:LOG_LEVEL_VERBOSE];
|
||||
#else
|
||||
[DDLog addLogger:[DDASLLogger sharedInstance]];
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
[DDLogLevel setLogLevel:(int)[defaults integerForKey:kDefaultLogLevel]];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)pruneProjectHistory
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSMutableArray *projectHistory = [[defaults arrayForKey:kDefaultXCCProjectHistory] mutableCopy];
|
||||
NSFileManager *fm = [NSFileManager new];
|
||||
|
||||
for (NSInteger i = projectHistory.count - 1; i >= 0; --i)
|
||||
{
|
||||
if (![fm fileExistsAtPath:projectHistory[i]])
|
||||
[projectHistory removeObjectAtIndex:i];
|
||||
}
|
||||
|
||||
NSInteger maxProjects = [defaults integerForKey:kDefaultMaxRecentProjects];
|
||||
|
||||
if (projectHistory.count > maxProjects)
|
||||
[projectHistory removeObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(maxProjects, projectHistory.count - maxProjects)]];
|
||||
|
||||
[defaults setObject:projectHistory forKey:kDefaultXCCProjectHistory];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (NSImage *)iconActive
|
||||
{
|
||||
if (!_iconActive)
|
||||
_iconActive = [NSImage imageNamed:@"icon-active"];
|
||||
|
||||
return _iconActive;
|
||||
}
|
||||
|
||||
- (NSImage *)iconInactive
|
||||
{
|
||||
if (!_iconInactive)
|
||||
_iconInactive = [NSImage imageNamed:@"icon-inactive"];
|
||||
|
||||
return _iconInactive;
|
||||
}
|
||||
|
||||
- (NSImage *)iconWorking
|
||||
{
|
||||
if (!_iconWorking)
|
||||
_iconWorking = [NSImage imageNamed:@"icon-working"];
|
||||
|
||||
return _iconWorking;
|
||||
}
|
||||
|
||||
- (NSImage *)iconError
|
||||
{
|
||||
if (!_iconError)
|
||||
_iconError = [NSImage imageNamed:@"icon-error"];
|
||||
|
||||
return _iconError;
|
||||
}
|
||||
|
||||
- (NSMenu *)recentMenu
|
||||
{
|
||||
if (!_recentMenu)
|
||||
{
|
||||
_recentMenu = [NSMenu new];
|
||||
_recentMenu.delegate = self;
|
||||
self.menuItemHistory.submenu = _recentMenu;
|
||||
}
|
||||
|
||||
return _recentMenu;
|
||||
}
|
||||
|
||||
#pragma mark - Notification handlers
|
||||
|
||||
- (void)batchDidStart:(NSNotification *)note
|
||||
{
|
||||
DDLogVerbose(@"Batch start");
|
||||
|
||||
self.statusItem.image = self.iconWorking;
|
||||
}
|
||||
|
||||
- (void)batchDidEnd:(NSNotification *)note
|
||||
{
|
||||
DDLogVerbose(@"Batch end");
|
||||
|
||||
if (!self.xcc.isLoadingProject)
|
||||
self.statusItem.image = self.xcc.hasErrors ? self.iconError : self.iconActive;
|
||||
}
|
||||
|
||||
- (void)projectDidFinishLoading:(NSNotification *)note
|
||||
{
|
||||
self.statusItem.image = self.xcc.hasErrors ? self.iconError : self.iconActive;
|
||||
self.menuItemListen.title = [NSString stringWithFormat:@"Close “%@”", self.xcc.projectPath.lastPathComponent];
|
||||
self.menuItemListen.action = @selector(closeProject:);
|
||||
}
|
||||
|
||||
// Watch changes to the max recent projects preference
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
{
|
||||
if ([keyPath isEqualToString:kDefaultMaxRecentProjects])
|
||||
[self pruneProjectHistory];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)loadProject:(id)aSender
|
||||
{
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
|
||||
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
||||
openPanel.title = @"Choose Cappuccino Project";
|
||||
openPanel.canChooseDirectories = YES;
|
||||
openPanel.canCreateDirectories = YES;
|
||||
openPanel.canChooseFiles = NO;
|
||||
|
||||
if ([openPanel runModal] != NSFileHandlingPanelOKButton)
|
||||
return;
|
||||
|
||||
NSString *projectPath = [[openPanel.URLs[0] path] stringByStandardizingPath];
|
||||
[self loadProjectAtPath:projectPath];
|
||||
}
|
||||
|
||||
- (void)closeProject:(id)aSender
|
||||
{
|
||||
[self.xcc stop];
|
||||
|
||||
self.statusItem.image = self.iconInactive;
|
||||
self.menuItemListen.title = @"Open Project…";
|
||||
self.menuItemListen.action = @selector(loadProject:);
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kDefaultLastOpenedPath];
|
||||
}
|
||||
|
||||
- (void)switchToProject:(NSMenuItem *)aSender
|
||||
{
|
||||
[self loadProjectAtPath:aSender.representedObject];
|
||||
}
|
||||
|
||||
- (void)clearProjectHistory:(id)aSender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[NSArray array] forKey:kDefaultXCCProjectHistory];
|
||||
[self updateHistoryMenu];
|
||||
}
|
||||
|
||||
- (IBAction)openHelp:(id)aSender
|
||||
{
|
||||
if (!self.helpView.document)
|
||||
{
|
||||
NSURL *helpURL = [[NSBundle mainBundle] URLForResource:@"help" withExtension:@"pdf"];
|
||||
PDFDocument *help = [[PDFDocument alloc] initWithURL:helpURL];
|
||||
self.helpView.document = help;
|
||||
}
|
||||
|
||||
[self openWindow:self.helpWindow];
|
||||
}
|
||||
|
||||
- (IBAction)openAbout:(id)aSender
|
||||
{
|
||||
[self openWindow:self.aboutWindow];
|
||||
}
|
||||
|
||||
- (IBAction)openPreferences:(id)aSender
|
||||
{
|
||||
[self openWindow:self.preferencesWindow];
|
||||
}
|
||||
|
||||
#pragma mark - Delegates
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app
|
||||
{
|
||||
[self.xcc stop];
|
||||
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)aMenuItem
|
||||
{
|
||||
NSMenu *menu = aMenuItem.menu;
|
||||
|
||||
if (aMenuItem == self.menuItemListen)
|
||||
{
|
||||
return !!self.xcc.projectPath;
|
||||
}
|
||||
else if (menu == self.recentMenu)
|
||||
{
|
||||
// Disable recent items if they don't exist or are not directories,
|
||||
// but enable the Clear History item, which is last in the menu.
|
||||
if ([menu indexOfItem:aMenuItem] == menu.itemArray.count - 1)
|
||||
return YES;
|
||||
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
|
||||
BOOL exists, isDirectory;
|
||||
exists = [fm fileExistsAtPath:aMenuItem.representedObject isDirectory:&isDirectory];
|
||||
|
||||
return exists && isDirectory;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Bindings
|
||||
|
||||
- (NSString *)bundleVersion
|
||||
{
|
||||
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
|
||||
}
|
||||
|
||||
#pragma mark - Private Helpers
|
||||
|
||||
- (void)loadProjectAtPath:(NSString *)path
|
||||
{
|
||||
if ([self.xcc.projectPath isEqualToString:path])
|
||||
return;
|
||||
|
||||
[self closeProject:self];
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSMutableArray *projectHistory = [[defaults arrayForKey:kDefaultXCCProjectHistory] mutableCopy];
|
||||
|
||||
if ([projectHistory containsObject:path])
|
||||
[projectHistory removeObject:path];
|
||||
|
||||
// The path may no longer be there, validate it
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
|
||||
BOOL exists, isDirectory;
|
||||
exists = [fm fileExistsAtPath:path isDirectory:&isDirectory];
|
||||
|
||||
if (exists && isDirectory)
|
||||
{
|
||||
[projectHistory insertObject:path atIndex:0];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
NSRunAlertPanel(@"Project not found.", @"%@ %@", nil, nil, nil, path, !exists ? @"no longer exists." : @"is not a directory.");
|
||||
}
|
||||
|
||||
[defaults setObject:projectHistory forKey:kDefaultXCCProjectHistory];
|
||||
[self pruneProjectHistory];
|
||||
[self updateHistoryMenu];
|
||||
|
||||
if (exists && isDirectory)
|
||||
[self.xcc loadProjectAtPath:path];
|
||||
}
|
||||
|
||||
- (void)openWindow:(NSWindow *)aWindow
|
||||
{
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
[aWindow makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
- (void)updateHistoryMenu
|
||||
{
|
||||
[self.recentMenu removeAllItems];
|
||||
NSArray *projectHistory = [[NSUserDefaults standardUserDefaults] arrayForKey:kDefaultXCCProjectHistory];
|
||||
|
||||
for (NSString *path in projectHistory)
|
||||
{
|
||||
NSMenuItem *item = [self.recentMenu addItemWithTitle:path.lastPathComponent action:@selector(switchToProject:) keyEquivalent:@""];
|
||||
[item setEnabled:YES];
|
||||
item.representedObject = path;
|
||||
}
|
||||
|
||||
[self.recentMenu addItem:[NSMenuItem separatorItem]];
|
||||
[self.recentMenu addItemWithTitle:@"Clear history" action:@selector(clearProjectHistory:) keyEquivalent:@""];
|
||||
|
||||
self.menuItemHistory.enabled = [projectHistory count] > 0;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// DDLogLevel.h
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/29/13.
|
||||
// Copyright (c) 2013 Cappuccino Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern int ddLogLevel;
|
||||
|
||||
@interface DDLogLevel : NSObject
|
||||
|
||||
+ (void)setLogLevel:(int)logLevel;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// DDLog.m
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/29/13.
|
||||
// Copyright (c) 2013 Cappuccino Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DDLogLevel.h"
|
||||
|
||||
|
||||
int ddLogLevel = LOG_LEVEL_OFF;
|
||||
|
||||
|
||||
@implementation DDLogLevel
|
||||
|
||||
+ (int)ddLogLevel
|
||||
{
|
||||
return ddLogLevel;
|
||||
}
|
||||
|
||||
+ (void)setLogLevel:(int)logLevel
|
||||
{
|
||||
ddLogLevel = logLevel;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Debug.xcconfig
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/18/13.
|
||||
//
|
||||
//
|
||||
|
||||
//:configuration = Debug
|
||||
ARCHS = $(ARCHS_STANDARD_64_BIT)
|
||||
SDKROOT = macosx
|
||||
ONLY_ACTIVE_ARCH = YES
|
||||
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
|
||||
COMBINE_HIDPI_IMAGES = YES
|
||||
INSTALL_PATH = $(LOCAL_APPS_DIR)
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.6.8
|
||||
COPY_PHASE_STRIP = NO
|
||||
INFOPLIST_FILE = XcodeCapp/Info.plist
|
||||
PRODUCT_NAME = XcodeCapp
|
||||
ALWAYS_SEARCH_USER_PATHS = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(SRCROOT)/XcodeCapp"
|
||||
GCC_OPTIMIZATION_LEVEL = 0
|
||||
CLANG_ENABLE_OBJC_ARC = YES
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES
|
||||
GCC_PREFIX_HEADER = XcodeCapp/XcodeCapp-Prefix.pch
|
||||
GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES
|
||||
GCC_WARN_UNUSED_VARIABLE = YES
|
||||
WRAPPER_EXTENSION = app
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99
|
||||
CLANG_CXX_LANGUAGE_STANDARD = gnu++0x
|
||||
CLANG_CXX_LIBRARY = libc++
|
||||
CLANG_WARN_EMPTY_BODY = YES
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES
|
||||
CLANG_WARN_ENUM_CONVERSION = YES
|
||||
CLANG_WARN_INT_CONVERSION = YES
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES
|
||||
GCC_WARN_UNUSED_VARIABLE = YES
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// FindSourceFilesOperation.h
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/27/13.
|
||||
// Copyright (c) 2013 Cappuccino Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class XcodeCapp;
|
||||
|
||||
|
||||
extern NSString * const XCCNeedSourceToProjectPathMappingNotification;
|
||||
|
||||
|
||||
@interface FindSourceFilesOperation : NSOperation
|
||||
|
||||
- (id)initWithXCC:(XcodeCapp *)xcc projectId:(NSNumber *)projectId path:(NSString *)path;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// FindSourceFilesOperation.m
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/27/13.
|
||||
// Copyright (c) 2013 Cappuccino Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FindSourceFilesOperation.h"
|
||||
#import "ProcessSourceOperation.h"
|
||||
#import "XcodeCapp.h"
|
||||
|
||||
NSString * const XCCNeedSourceToProjectPathMappingNotification = @"XCCNeedSourceToProjectPathMappingNotification";
|
||||
|
||||
|
||||
@interface FindSourceFilesOperation ()
|
||||
|
||||
@property XcodeCapp *xcc;
|
||||
@property NSNumber *projectId;
|
||||
@property NSString *projectPathToSearch;
|
||||
@property NSString *projectPath;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation FindSourceFilesOperation
|
||||
|
||||
- (id)initWithXCC:(XcodeCapp *)xcc projectId:(NSNumber *)projectId path:(NSString *)path
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
self.xcc = xcc;
|
||||
self.projectId = projectId;
|
||||
self.projectPathToSearch = path;
|
||||
self.projectPath = xcc.projectPath;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)main
|
||||
{
|
||||
[self findSourceFilesAtProjectPath:self.projectPathToSearch];
|
||||
}
|
||||
|
||||
- (void)findSourceFilesAtProjectPath:(NSString *)aProjectPath
|
||||
{
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
DDLogVerbose(@"-->findSourceFiles: %@", aProjectPath);
|
||||
|
||||
NSError *error = NULL;
|
||||
NSString *projectPath = [self.projectPath stringByAppendingPathComponent:aProjectPath];
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
|
||||
NSArray *urls = [fm contentsOfDirectoryAtURL:[NSURL fileURLWithPath:projectPath.stringByResolvingSymlinksInPath]
|
||||
includingPropertiesForKeys:@[NSURLIsDirectoryKey, NSURLIsSymbolicLinkKey]
|
||||
options:NSDirectoryEnumerationSkipsHiddenFiles | NSDirectoryEnumerationSkipsPackageDescendants | NSDirectoryEnumerationSkipsSubdirectoryDescendants
|
||||
error:&error];
|
||||
|
||||
if (!urls)
|
||||
return;
|
||||
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
|
||||
for (NSURL *url in urls)
|
||||
{
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
NSString *filename = url.lastPathComponent;
|
||||
|
||||
NSString *projectRelativePath = [aProjectPath stringByAppendingPathComponent:filename];
|
||||
NSString *realPath = url.path;
|
||||
NSURL *resolvedURL = url;
|
||||
|
||||
NSNumber *isDirectory, *isSymlink;
|
||||
[url getResourceValue:&isSymlink forKey:NSURLIsSymbolicLinkKey error:nil];
|
||||
|
||||
if (isSymlink.boolValue == YES)
|
||||
{
|
||||
resolvedURL = [url URLByResolvingSymlinksInPath];
|
||||
|
||||
if ([resolvedURL checkResourceIsReachableAndReturnError:nil])
|
||||
{
|
||||
filename = resolvedURL.lastPathComponent;
|
||||
realPath = resolvedURL.path;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
[resolvedURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
|
||||
|
||||
if (isDirectory.boolValue == YES)
|
||||
{
|
||||
if ([self.xcc shouldIgnoreDirectoryNamed:filename])
|
||||
{
|
||||
DDLogVerbose(@"ignored symlinked directory: %@", projectRelativePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the resolved path is not within the project directory and is not ignored, add a mapping to it
|
||||
// so we can map the resolved path back to the project directory later.
|
||||
if (isSymlink.boolValue == YES)
|
||||
{
|
||||
NSString *fullProjectPath = [self.projectPath stringByAppendingPathComponent:projectRelativePath];
|
||||
|
||||
if (![realPath hasPrefix:fullProjectPath] && ![self.xcc pathMatchesIgnoredPaths:fullProjectPath])
|
||||
{
|
||||
DDLogVerbose(@"symlinked directory: %@ -> %@", projectRelativePath, realPath);
|
||||
|
||||
NSDictionary *info =
|
||||
@{
|
||||
@"projectId":self.projectId,
|
||||
@"sourcePath":realPath,
|
||||
@"projectPath":fullProjectPath
|
||||
};
|
||||
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
[center postNotificationName:XCCNeedSourceToProjectPathMappingNotification object:self userInfo:info];
|
||||
}
|
||||
else
|
||||
DDLogVerbose(@"ignored symlinked directory: %@", projectRelativePath);
|
||||
}
|
||||
|
||||
[self findSourceFilesAtProjectPath:projectRelativePath];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
if ([self.xcc pathMatchesIgnoredPaths:realPath])
|
||||
continue;
|
||||
|
||||
NSString *projectSourcePath = [self.projectPath stringByAppendingPathComponent:projectRelativePath];
|
||||
|
||||
if ([self.xcc isObjjFile:filename] || [self.xcc isXibFile:filename])
|
||||
{
|
||||
NSString *processedPath;
|
||||
|
||||
if ([self.xcc isObjjFile:filename])
|
||||
processedPath = [[self.xcc shadowBasePathForProjectSourcePath:projectSourcePath] stringByAppendingPathExtension:@"h"];
|
||||
else
|
||||
processedPath = [projectSourcePath.stringByDeletingPathExtension stringByAppendingPathExtension:@"cib"];
|
||||
|
||||
if (![fm fileExistsAtPath:processedPath])
|
||||
[self createProcessingOperationForProjectSourcePath:projectSourcePath];
|
||||
}
|
||||
}
|
||||
|
||||
DDLogVerbose(@"<--findSourceFiles: %@", aProjectPath);
|
||||
}
|
||||
|
||||
- (void)createProcessingOperationForProjectSourcePath:(NSString *)projectSourcePath
|
||||
{
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
ProcessSourceOperation *op = [[ProcessSourceOperation alloc] initWithXCC:self.xcc
|
||||
projectId:self.projectId
|
||||
sourcePath:projectSourcePath];
|
||||
[[NSOperationQueue currentQueue] addOperation:op];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,5 @@
|
||||
#include <Growl/GrowlDefines.h>
|
||||
|
||||
#ifdef __OBJC__
|
||||
# include <Growl/GrowlApplicationBridge.h>
|
||||
#endif
|
||||
@@ -18,14 +18,11 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
#import "GrowlDefines.h"
|
||||
#import <Growl/GrowlDefines.h>
|
||||
|
||||
//Forward declarations
|
||||
@protocol GrowlApplicationBridgeDelegate;
|
||||
|
||||
//Internal notification when the user chooses not to install (to avoid continuing to cache notifications awaiting installation)
|
||||
#define GROWL_USER_CHOSE_NOT_TO_INSTALL_NOTIFICATION @"User chose not to install"
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
#pragma mark -
|
||||
|
||||
@@ -45,9 +42,9 @@
|
||||
* @method isGrowlInstalled
|
||||
* @abstract Detects whether Growl is installed.
|
||||
* @discussion Determines if the Growl prefpane and its helper app are installed.
|
||||
* @result Returns YES if Growl is installed, NO otherwise.
|
||||
* @result this method will forever return YES.
|
||||
*/
|
||||
+ (BOOL) isGrowlInstalled;
|
||||
+ (BOOL) isGrowlInstalled __attribute__((deprecated));
|
||||
|
||||
/*!
|
||||
* @method isGrowlRunning
|
||||
@@ -57,6 +54,34 @@
|
||||
*/
|
||||
+ (BOOL) isGrowlRunning;
|
||||
|
||||
|
||||
/*!
|
||||
* @method isMistEnabled
|
||||
* @abstract Gives the caller a fairly good indication of whether or not built-in notifications(Mist) will be used.
|
||||
* @discussion since this call makes use of isGrowlRunning it is entirely possible for this value to change between call and
|
||||
* executing a notification dispatch
|
||||
* @result Returns YES if Growl isn't reachable and the developer has not opted-out of
|
||||
* Mist and the user hasn't set the global mist enable key to false.
|
||||
*/
|
||||
+ (BOOL)isMistEnabled;
|
||||
|
||||
/*!
|
||||
* @method setShouldUseBuiltInNotifications
|
||||
* @abstract opt-out mechanism for the mist notification style in the event growl can't be reached.
|
||||
* @discussion if growl is unavailable due to not being installed or as a result of being turned off then
|
||||
* this option can enable/disable a built-in fire and forget display style
|
||||
* @param should Specifies whether or not the developer wants to opt-in (default) or opt out
|
||||
* of the built-in Mist style in the event Growl is unreachable.
|
||||
*/
|
||||
+ (void)setShouldUseBuiltInNotifications:(BOOL)should;
|
||||
|
||||
/*!
|
||||
* @method shouldUseBuiltInNotifications
|
||||
* @abstract returns the current opt-in state of the framework's use of the Mist display style.
|
||||
* @result Returns NO if the developer opt-ed out of Mist, the default value is YES.
|
||||
*/
|
||||
+ (BOOL)shouldUseBuiltInNotifications;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/*!
|
||||
@@ -87,7 +112,7 @@
|
||||
*
|
||||
* @param inDelegate The delegate for the GrowlApplicationBridge. It must conform to the GrowlApplicationBridgeDelegate protocol.
|
||||
*/
|
||||
+ (void) setGrowlDelegate:(NSObject<GrowlApplicationBridgeDelegate> *)inDelegate;
|
||||
+ (void) setGrowlDelegate:(id<GrowlApplicationBridgeDelegate>)inDelegate;
|
||||
|
||||
/*!
|
||||
* @method growlDelegate
|
||||
@@ -95,7 +120,7 @@
|
||||
* @discussion See setGrowlDelegate: for details.
|
||||
* @result The Growl delegate.
|
||||
*/
|
||||
+ (NSObject<GrowlApplicationBridgeDelegate> *) growlDelegate;
|
||||
+ (id<GrowlApplicationBridgeDelegate>) growlDelegate;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@@ -235,6 +260,7 @@
|
||||
* Growl when next it is ready; <code>NO</code> if not.
|
||||
*/
|
||||
+ (void) setWillRegisterWhenGrowlIsReady:(BOOL)flag;
|
||||
|
||||
/*! @method willRegisterWhenGrowlIsReady
|
||||
* @abstract Reports whether GrowlApplicationBridge will register with Growl
|
||||
* when Growl next launches.
|
||||
@@ -323,7 +349,7 @@
|
||||
* Key Value
|
||||
* --- -----
|
||||
* <code>GROWL_APP_NAME</code> <code>CFBundleExecutableName</code>
|
||||
* <code>GROWL_APP_ICON</code> The icon of the application.
|
||||
* <code>GROWL_APP_ICON_DATA</code> The data of the icon of the application.
|
||||
* <code>GROWL_APP_LOCATION</code> The location of the application.
|
||||
* <code>GROWL_NOTIFICATIONS_DEFAULT</code> <code>GROWL_NOTIFICATIONS_ALL</code>
|
||||
*
|
||||
@@ -336,6 +362,7 @@
|
||||
* copy of <code>regDict</code>.
|
||||
*/
|
||||
+ (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict;
|
||||
|
||||
/*! @method registrationDictionaryByFillingInDictionary:restrictToKeys:
|
||||
* @abstract Tries to fill in missing keys in a registration dictionary.
|
||||
* @discussion This method examines the passed-in dictionary for missing keys,
|
||||
@@ -344,7 +371,7 @@
|
||||
* Key Value
|
||||
* --- -----
|
||||
* <code>GROWL_APP_NAME</code> <code>CFBundleExecutableName</code>
|
||||
* <code>GROWL_APP_ICON</code> The icon of the application.
|
||||
* <code>GROWL_APP_ICON_DATA</code> The data of the icon of the application.
|
||||
* <code>GROWL_APP_LOCATION</code> The location of the application.
|
||||
* <code>GROWL_NOTIFICATIONS_DEFAULT</code> <code>GROWL_NOTIFICATIONS_ALL</code>
|
||||
*
|
||||
@@ -368,13 +395,39 @@
|
||||
* the keys that it will look for are:
|
||||
*
|
||||
* \li <code>GROWL_APP_NAME</code>
|
||||
* \li <code>GROWL_APP_ICON</code>
|
||||
* \li <code>GROWL_APP_ICON_DATA</code>
|
||||
*
|
||||
* @since Growl.framework 1.1
|
||||
*/
|
||||
+ (NSDictionary *) notificationDictionaryByFillingInDictionary:(NSDictionary *)regDict;
|
||||
|
||||
+ (NSDictionary *) frameworkInfoDictionary;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/*!
|
||||
*@method growlURLSchemeAvailable
|
||||
*@abstract Lets the app know whether growl:// is registered on the system, used for certain methods below this
|
||||
*@return Returns whether growl:// is registered on the system
|
||||
*@discussion Methods such as openGrowlPreferences rely on the growl:// URL scheme to function
|
||||
* Further, this method can provide a check on whether Growl is installed,
|
||||
* however, the framework will not be relying on this method for choosing when/how to notify,
|
||||
* and it is not recommended that the app rely on it for other than whether to use growl:// methods
|
||||
*@since Growl.framework 1.4
|
||||
*/
|
||||
+ (BOOL) isGrowlURLSchemeAvailable;
|
||||
|
||||
/*!
|
||||
* @method openGrowlPreferences:
|
||||
* @abstract Open Growl preferences, optionally to this app's settings, growl:// method
|
||||
* @param showApp Whether to show the application's settings, otherwise just opens to the last position
|
||||
* @return Return's whether opening the URL was succesfull or not.
|
||||
* @discussion Will launch if Growl is installed, but not running, and open the preferences window
|
||||
* Uses growl:// URL scheme
|
||||
* @since Growl.framework 1.4
|
||||
*/
|
||||
+ (BOOL) openGrowlPreferences:(BOOL)showApp;
|
||||
|
||||
@end
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -383,27 +436,15 @@
|
||||
/*!
|
||||
* @protocol GrowlApplicationBridgeDelegate
|
||||
* @abstract Required protocol for the Growl delegate.
|
||||
* @discussion The methods in this protocol are required and are called
|
||||
* @discussion The methods in this protocol are optional and are called
|
||||
* automatically as needed by GrowlApplicationBridge. See
|
||||
* <code>+[GrowlApplicationBridge setGrowlDelegate:]</code>.
|
||||
* See also <code>GrowlApplicationBridgeDelegate_InformalProtocol</code>.
|
||||
*/
|
||||
|
||||
@protocol GrowlApplicationBridgeDelegate
|
||||
@protocol GrowlApplicationBridgeDelegate <NSObject>
|
||||
|
||||
// -registrationDictionaryForGrowl has moved to the informal protocol as of 0.7.
|
||||
|
||||
@end
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
#pragma mark -
|
||||
|
||||
/*!
|
||||
* @category NSObject(GrowlApplicationBridgeDelegate_InformalProtocol)
|
||||
* @abstract Methods which may be optionally implemented by the GrowlDelegate.
|
||||
* @discussion The methods in this informal protocol will only be called if implemented by the delegate.
|
||||
*/
|
||||
@interface NSObject (GrowlApplicationBridgeDelegate_InformalProtocol)
|
||||
@optional
|
||||
|
||||
/*!
|
||||
* @method registrationDictionaryForGrowl
|
||||
@@ -510,66 +551,17 @@
|
||||
*/
|
||||
- (void) growlNotificationTimedOut:(id)clickContext;
|
||||
|
||||
|
||||
/*!
|
||||
* @method hasNetworkClientEntitlement
|
||||
* @abstract Used only in sandboxed situations since we don't know whether the app has com.apple.security.network.client entitlement
|
||||
* @discussion GrowlDelegate calls to find out if we have the com.apple.security.network.client entitlement,
|
||||
* since we can't find this out without hitting the sandbox. We only call it if we detect that the application is sandboxed.
|
||||
*/
|
||||
- (BOOL) hasNetworkClientEntitlement;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
/*!
|
||||
* @category NSObject(GrowlApplicationBridgeDelegate_Installation_InformalProtocol)
|
||||
* @abstract Methods which may be optionally implemented by the Growl delegate when used with Growl-WithInstaller.framework.
|
||||
* @discussion The methods in this informal protocol will only be called if
|
||||
* implemented by the delegate. They allow greater control of the information
|
||||
* presented to the user when installing or upgrading Growl from within your
|
||||
* application when using Growl-WithInstaller.framework.
|
||||
*/
|
||||
@interface NSObject (GrowlApplicationBridgeDelegate_Installation_InformalProtocol)
|
||||
|
||||
/*!
|
||||
* @method growlInstallationWindowTitle
|
||||
* @abstract Return the title of the installation window.
|
||||
* @discussion If not implemented, Growl will use a default, localized title.
|
||||
* @result An NSString object to use as the title.
|
||||
*/
|
||||
- (NSString *)growlInstallationWindowTitle;
|
||||
|
||||
/*!
|
||||
* @method growlUpdateWindowTitle
|
||||
* @abstract Return the title of the upgrade window.
|
||||
* @discussion If not implemented, Growl will use a default, localized title.
|
||||
* @result An NSString object to use as the title.
|
||||
*/
|
||||
- (NSString *)growlUpdateWindowTitle;
|
||||
|
||||
/*!
|
||||
* @method growlInstallationInformation
|
||||
* @abstract Return the information to display when installing.
|
||||
* @discussion This information may be as long or short as desired (the window
|
||||
* will be sized to fit it). It will be displayed to the user as an
|
||||
* explanation of what Growl is and what it can do in your application. It
|
||||
* should probably note that no download is required to install.
|
||||
*
|
||||
* If this is not implemented, Growl will use a default, localized explanation.
|
||||
* @result An NSAttributedString object to display.
|
||||
*/
|
||||
- (NSAttributedString *)growlInstallationInformation;
|
||||
|
||||
/*!
|
||||
* @method growlUpdateInformation
|
||||
* @abstract Return the information to display when upgrading.
|
||||
* @discussion This information may be as long or short as desired (the window
|
||||
* will be sized to fit it). It will be displayed to the user as an
|
||||
* explanation that an updated version of Growl is included in your
|
||||
* application and no download is required.
|
||||
*
|
||||
* If this is not implemented, Growl will use a default, localized explanation.
|
||||
* @result An NSAttributedString object to display.
|
||||
*/
|
||||
- (NSAttributedString *)growlUpdateInformation;
|
||||
|
||||
@end
|
||||
|
||||
//private
|
||||
@interface GrowlApplicationBridge (GrowlInstallationPrompt_private)
|
||||
+ (void) _userChoseNotToInstallGrowl;
|
||||
@end
|
||||
|
||||
#endif /* __GrowlApplicationBridge_h__ */
|
||||
@@ -7,10 +7,8 @@
|
||||
|
||||
#ifdef __OBJC__
|
||||
#define XSTR(x) (@x)
|
||||
#define STRING_TYPE NSString *
|
||||
#else
|
||||
#define XSTR CFSTR
|
||||
#define STRING_TYPE CFStringRef
|
||||
#endif
|
||||
|
||||
/*! @header GrowlDefines.h
|
||||
@@ -56,7 +54,7 @@
|
||||
* This key is optional.
|
||||
*/
|
||||
#define GROWL_APP_ID XSTR("ApplicationId")
|
||||
/*! @defined GROWL_APP_ICON
|
||||
/*! @defined GROWL_APP_ICON_DATA
|
||||
* @abstract The image data for your application's icon.
|
||||
* @discussion Image data representing your application's icon. This may be
|
||||
* superimposed on a notification icon as a badge, used as the notification
|
||||
@@ -66,7 +64,7 @@
|
||||
*
|
||||
* Optional. Not supported by all display plugins.
|
||||
*/
|
||||
#define GROWL_APP_ICON XSTR("ApplicationIcon")
|
||||
#define GROWL_APP_ICON_DATA XSTR("ApplicationIcon")
|
||||
/*! @defined GROWL_NOTIFICATIONS_DEFAULT
|
||||
* @abstract The array of notifications to turn on by default.
|
||||
* @discussion These are the names of the notifications that should be enabled
|
||||
@@ -101,6 +99,14 @@
|
||||
* This key is optional.
|
||||
*/
|
||||
#define GROWL_NOTIFICATIONS_DESCRIPTIONS XSTR("NotificationDescriptions")
|
||||
/*! @defined GROWL_NOTIFICATIONS_ICONS
|
||||
* @abstract A dictionary of icons for each notification
|
||||
* @discussion This is an NSDictionary whose keys are GROWL_NOTIFICATION_NAME strings and whose objects are
|
||||
* icons for each notification, for GNTP spec
|
||||
*
|
||||
* This key is optional.
|
||||
*/
|
||||
#define GROWL_NOTIFICATIONS_ICONS XSTR("NotificationIcons")
|
||||
|
||||
/*! @defined GROWL_TICKET_VERSION
|
||||
* @abstract The version of your registration ticket.
|
||||
@@ -144,20 +150,20 @@
|
||||
*/
|
||||
#define GROWL_NOTIFICATION_DESCRIPTION XSTR("NotificationDescription")
|
||||
/*! @defined GROWL_NOTIFICATION_ICON
|
||||
* @discussion Image data for the notification icon. Must be in a format
|
||||
* @discussion Image data for the notification icon. Image data must be in a format
|
||||
* supported by NSImage, such as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF.
|
||||
*
|
||||
* Optional. Not supported by all display plugins.
|
||||
*/
|
||||
#define GROWL_NOTIFICATION_ICON XSTR("NotificationIcon")
|
||||
#define GROWL_NOTIFICATION_ICON_DATA XSTR("NotificationIcon")
|
||||
/*! @defined GROWL_NOTIFICATION_APP_ICON
|
||||
* @discussion Image data for the application icon, in case GROWL_APP_ICON does
|
||||
* not apply for some reason. Must be in a format supported by NSImage, such
|
||||
* not apply for some reason. Image data be in a format supported by NSImage, such
|
||||
* as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF.
|
||||
*
|
||||
* Optional. Not supported by all display plugins.
|
||||
*/
|
||||
#define GROWL_NOTIFICATION_APP_ICON XSTR("NotificationAppIcon")
|
||||
#define GROWL_NOTIFICATION_APP_ICON_DATA XSTR("NotificationAppIcon")
|
||||
/*! @defined GROWL_NOTIFICATION_PRIORITY
|
||||
* @discussion The priority of the notification as an integer number from
|
||||
* -2 to +2 (+2 being highest).
|
||||
@@ -185,16 +191,6 @@
|
||||
*/
|
||||
#define GROWL_NOTIFICATION_CLICK_CONTEXT XSTR("NotificationClickContext")
|
||||
|
||||
/*! @defined GROWL_DISPLAY_PLUGIN
|
||||
* @discussion The name of a display plugin which should be used for this notification.
|
||||
* Optional. If this key is not set or the specified display plugin does not
|
||||
* exist, the display plugin stored in the application ticket is used. This key
|
||||
* allows applications to use different default display plugins for their
|
||||
* notifications. The user can still override those settings in the preference
|
||||
* pane.
|
||||
*/
|
||||
#define GROWL_DISPLAY_PLUGIN XSTR("NotificationDisplayPlugin")
|
||||
|
||||
/*! @defined GROWL_NOTIFICATION_IDENTIFIER
|
||||
* @abstract An identifier for the notification for coalescing purposes.
|
||||
* Notifications with the same identifier fall into the same class; only
|
||||
@@ -224,6 +220,19 @@
|
||||
*/
|
||||
#define GROWL_NOTIFICATION_PROGRESS XSTR("NotificationProgress")
|
||||
|
||||
/*! @defined GROWL_NOTIFICATION_ALREADY_SHOWN
|
||||
* @abstract If this key is set, it should contain a bool value wrapped
|
||||
* in a NSNumber which describes whether the notification has
|
||||
* already been displayed, for instance by built in Notification
|
||||
* Center support. This value can be used to allow display
|
||||
* plugins to skip a notification, while still allowing Growl
|
||||
* actions to run on them.
|
||||
*
|
||||
* Optional. Not supported by all display plugins.
|
||||
*/
|
||||
#define GROWL_NOTIFICATION_ALREADY_SHOWN XSTR("AlreadyShown")
|
||||
|
||||
|
||||
// Notifications
|
||||
#pragma mark Notifications
|
||||
|
||||
@@ -245,7 +254,7 @@
|
||||
* The userInfo dictionary for this notification can contain these keys:
|
||||
* <ul>
|
||||
* <li>GROWL_APP_NAME</li>
|
||||
* <li>GROWL_APP_ICON</li>
|
||||
* <li>GROWL_APP_ICON_DATA</li>
|
||||
* <li>GROWL_NOTIFICATIONS_ALL</li>
|
||||
* <li>GROWL_NOTIFICATIONS_DEFAULT</li>
|
||||
* </ul>
|
||||
@@ -288,12 +297,6 @@
|
||||
* Growl_PostNotification.
|
||||
*/
|
||||
#define GROWL_NOTIFICATION XSTR("GrowlNotification")
|
||||
/*! @defined GROWL_SHUTDOWN
|
||||
* @abstract The distributed notification name that tells Growl to shutdown.
|
||||
* @discussion The Growl preference pane posts this notification when the
|
||||
* "Stop Growl" button is clicked.
|
||||
*/
|
||||
#define GROWL_SHUTDOWN XSTR("GrowlShutdown")
|
||||
/*! @defined GROWL_PING
|
||||
* @abstract A distributed notification to check whether Growl is running.
|
||||
* @discussion This is used by the Growl preference pane. If it receives a
|
||||
@@ -313,15 +316,48 @@
|
||||
* registration dictionary supplied by its delegate.
|
||||
*/
|
||||
#define GROWL_IS_READY XSTR("Lend Me Some Sugar; I Am Your Neighbor!")
|
||||
/*! @defined GROWL_NOTIFICATION_CLICKED
|
||||
* @abstract The distributed notification sent when a supported notification is clicked.
|
||||
|
||||
|
||||
/*! @defined GROWL_DISTRIBUTED_NOTIFICATION_CLICKED_SUFFIX
|
||||
* @abstract Part of the name of the distributed notification sent when a supported notification is clicked.
|
||||
* @discussion When a Growl notification with a click context is clicked on by
|
||||
* the user, Growl posts this distributed notification.
|
||||
* The GrowlApplicationBridge responds to this notification by calling a
|
||||
* callback in its delegate.
|
||||
* the user, Growl posts a distributed notification whose name is in the format:
|
||||
* [NSString stringWithFormat:@"%@-%d-%@", appName, pid, GROWL_DISTRIBUTED_NOTIFICATION_CLICKED_SUFFIX]
|
||||
* The GrowlApplicationBridge responds to this notification by calling a callback in its delegate.
|
||||
*/
|
||||
#define GROWL_NOTIFICATION_CLICKED XSTR("GrowlClicked!")
|
||||
#define GROWL_NOTIFICATION_TIMED_OUT XSTR("GrowlTimedOut!")
|
||||
#define GROWL_DISTRIBUTED_NOTIFICATION_CLICKED_SUFFIX XSTR("GrowlClicked!")
|
||||
|
||||
/*! @defined GROWL_DISTRIBUTED_NOTIFICATION_TIMED_OUT_SUFFIX
|
||||
* @abstract Part of the name of the distributed notification sent when a supported notification times out without being clicked.
|
||||
* @discussion When a Growl notification with a click context times out, Growl posts a distributed notification
|
||||
* whose name is in the format:
|
||||
* [NSString stringWithFormat:@"%@-%d-%@", appName, pid, GROWL_DISTRIBUTED_NOTIFICATION_TIMED_OUT_SUFFIX]
|
||||
* The GrowlApplicationBridge responds to this notification by calling a callback in its delegate.
|
||||
* NOTE: The user may have actually clicked the 'close' button; this triggers an *immediate* time-out of the notification.
|
||||
*/
|
||||
#define GROWL_DISTRIBUTED_NOTIFICATION_TIMED_OUT_SUFFIX XSTR("GrowlTimedOut!")
|
||||
|
||||
/*! @defined GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_ON
|
||||
* @abstract The distributed notification sent when the Notification Center support is toggled on in Growl 2.0
|
||||
* @discussion When the user enables Notification Center support in Growl 2.0, this notification is sent
|
||||
* to inform all running apps that they should now speak to Notification Center directly.
|
||||
*/
|
||||
#define GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_ON XSTR("GrowlNotificationCenterOn!")
|
||||
|
||||
/*! @defined GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_OFF
|
||||
* @abstract The distributed notification sent when the Notification Center support is toggled off in Growl 2.0
|
||||
* @discussion When the user enables Notification Center support in Growl 2.0, this notification is sent
|
||||
* to inform all running apps that they should no longer speak to Notification Center directly.
|
||||
*/
|
||||
#define GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_OFF XSTR("GrowlNotificationCenterOff!")
|
||||
|
||||
/*! @defined GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_QUERY
|
||||
* @abstract The distributed notification sent by an application to query Growl 2.0's notification center support.
|
||||
* @discussion When an app starts up, it will send this query to get Growl 2.0 to spit out whether notification
|
||||
* center support is on or off.
|
||||
*/
|
||||
#define GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_QUERY XSTR("GrowlNotificationCenterYN?")
|
||||
|
||||
|
||||
/*! @group Other symbols */
|
||||
/* Symbols which don't fit into any of the other categories. */
|
||||
@@ -345,4 +381,6 @@
|
||||
|
||||
#define GROWL_POSITION_PREFERENCE_KEY @"GrowlSelectedPosition"
|
||||
|
||||
#define GROWL_PLUGIN_CONFIG_ID XSTR("GrowlPluginConfigurationID")
|
||||
|
||||
#endif //ndef _GROWLDEFINES_H
|
||||
@@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>12C60</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
@@ -13,11 +15,25 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2.1</string>
|
||||
<string>2.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>GRRR</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.2.1</string>
|
||||
<string>2.0.1</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>4G2008a</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>12C37</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.8</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0452</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>4G2008a</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>GrowlApplicationBridge</string>
|
||||
</dict>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>files</key>
|
||||
<dict>
|
||||
<key>Resources/Info.plist</key>
|
||||
<data>
|
||||
VZb3f8My4te/5JwcjfvotgCXTAs=
|
||||
</data>
|
||||
</dict>
|
||||
<key>rules</key>
|
||||
<dict>
|
||||
<key>^Resources/</key>
|
||||
<true/>
|
||||
<key>^Resources/.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^version.plist$</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -17,11 +17,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0</string>
|
||||
<string>3.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.0</string>
|
||||
<string>3.0.0</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.developer-tools</string>
|
||||
<key>LSUIElement</key>
|
||||
@@ -30,5 +30,14 @@
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>XCCCompatibilityVersion</key>
|
||||
<real>3</real>
|
||||
<key>XCCMandatoryExecutables</key>
|
||||
<array>
|
||||
<string>python</string>
|
||||
<string>narwhal-jsc</string>
|
||||
<string>objj</string>
|
||||
<string>nib2cib</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,41 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <asl.h>
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
*
|
||||
*
|
||||
* This class provides a logger for the Apple System Log facility.
|
||||
*
|
||||
* As described in the "Getting Started" page,
|
||||
* the traditional NSLog() function directs it's output to two places:
|
||||
*
|
||||
* - Apple System Log
|
||||
* - StdErr (if stderr is a TTY) so log statements show up in Xcode console
|
||||
*
|
||||
* To duplicate NSLog() functionality you can simply add this logger and a tty logger.
|
||||
* However, if you instead choose to use file logging (for faster performance),
|
||||
* you may choose to use a file logger and a tty logger.
|
||||
**/
|
||||
|
||||
@interface DDASLLogger : DDAbstractLogger <DDLogger>
|
||||
{
|
||||
aslclient client;
|
||||
}
|
||||
|
||||
+ (DDASLLogger *)sharedInstance;
|
||||
|
||||
// Inherited from DDAbstractLogger
|
||||
|
||||
// - (id <DDLogFormatter>)logFormatter;
|
||||
// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,99 @@
|
||||
#import "DDASLLogger.h"
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
**/
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
|
||||
@implementation DDASLLogger
|
||||
|
||||
static DDASLLogger *sharedInstance;
|
||||
|
||||
/**
|
||||
* The runtime sends initialize to each class in a program exactly one time just before the class,
|
||||
* or any class that inherits from it, is sent its first message from within the program. (Thus the
|
||||
* method may never be invoked if the class is not used.) The runtime sends the initialize message to
|
||||
* classes in a thread-safe manner. Superclasses receive this message before their subclasses.
|
||||
*
|
||||
* This method may also be called directly (assumably by accident), hence the safety mechanism.
|
||||
**/
|
||||
+ (void)initialize
|
||||
{
|
||||
static BOOL initialized = NO;
|
||||
if (!initialized)
|
||||
{
|
||||
initialized = YES;
|
||||
|
||||
sharedInstance = [[DDASLLogger alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
+ (DDASLLogger *)sharedInstance
|
||||
{
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (sharedInstance != nil)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ((self = [super init]))
|
||||
{
|
||||
// A default asl client is provided for the main thread,
|
||||
// but background threads need to create their own client.
|
||||
|
||||
client = asl_open(NULL, "com.apple.console", 0);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)logMessage:(DDLogMessage *)logMessage
|
||||
{
|
||||
NSString *logMsg = logMessage->logMsg;
|
||||
|
||||
if (formatter)
|
||||
{
|
||||
logMsg = [formatter formatLogMessage:logMessage];
|
||||
}
|
||||
|
||||
if (logMsg)
|
||||
{
|
||||
const char *msg = [logMsg UTF8String];
|
||||
|
||||
int aslLogLevel;
|
||||
switch (logMessage->logLevel)
|
||||
{
|
||||
// Note: By default ASL will filter anything above level 5 (Notice).
|
||||
// So our mappings shouldn't go above that level.
|
||||
|
||||
case 1 : aslLogLevel = ASL_LEVEL_CRIT; break;
|
||||
case 2 : aslLogLevel = ASL_LEVEL_ERR; break;
|
||||
case 3 : aslLogLevel = ASL_LEVEL_WARNING; break;
|
||||
default : aslLogLevel = ASL_LEVEL_NOTICE; break;
|
||||
}
|
||||
|
||||
asl_log(client, NULL, aslLogLevel, "%s", msg);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)loggerName
|
||||
{
|
||||
return @"cocoa.lumberjack.aslLogger";
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,597 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
*
|
||||
* Otherwise, here is a quick refresher.
|
||||
* There are three steps to using the macros:
|
||||
*
|
||||
* Step 1:
|
||||
* Import the header in your implementation file:
|
||||
*
|
||||
* #import "DDLog.h"
|
||||
*
|
||||
* Step 2:
|
||||
* Define your logging level in your implementation file:
|
||||
*
|
||||
* // Log levels: off, error, warn, info, verbose
|
||||
* static const int ddLogLevel = LOG_LEVEL_VERBOSE;
|
||||
*
|
||||
* Step 3:
|
||||
* Replace your NSLog statements with DDLog statements according to the severity of the message.
|
||||
*
|
||||
* NSLog(@"Fatal error, no dohickey found!"); -> DDLogError(@"Fatal error, no dohickey found!");
|
||||
*
|
||||
* DDLog works exactly the same as NSLog.
|
||||
* This means you can pass it multiple variables just like NSLog.
|
||||
**/
|
||||
|
||||
|
||||
@class DDLogMessage;
|
||||
|
||||
@protocol DDLogger;
|
||||
@protocol DDLogFormatter;
|
||||
|
||||
/**
|
||||
* This is the single macro that all other macros below compile into.
|
||||
* This big multiline macro makes all the other macros easier to read.
|
||||
**/
|
||||
|
||||
#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \
|
||||
[DDLog log:isAsynchronous \
|
||||
level:lvl \
|
||||
flag:flg \
|
||||
context:ctx \
|
||||
file:__FILE__ \
|
||||
function:fnct \
|
||||
line:__LINE__ \
|
||||
tag:atag \
|
||||
format:(frmt), ##__VA_ARGS__]
|
||||
|
||||
/**
|
||||
* Define the Objective-C and C versions of the macro.
|
||||
* These automatically inject the proper function name for either an objective-c method or c function.
|
||||
*
|
||||
* We also define shorthand versions for asynchronous and synchronous logging.
|
||||
**/
|
||||
|
||||
#define LOG_OBJC_MACRO(async, lvl, flg, ctx, frmt, ...) \
|
||||
LOG_MACRO(async, lvl, flg, ctx, nil, sel_getName(_cmd), frmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_C_MACRO(async, lvl, flg, ctx, frmt, ...) \
|
||||
LOG_MACRO(async, lvl, flg, ctx, nil, __FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define SYNC_LOG_OBJC_MACRO(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_OBJC_MACRO( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define ASYNC_LOG_OBJC_MACRO(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_OBJC_MACRO(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define SYNC_LOG_C_MACRO(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_C_MACRO( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define ASYNC_LOG_C_MACRO(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_C_MACRO(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Define version of the macro that only execute if the logLevel is above the threshold.
|
||||
* The compiled versions essentially look like this:
|
||||
*
|
||||
* if (logFlagForThisLogMsg & ddLogLevel) { execute log message }
|
||||
*
|
||||
* As shown further below, Lumberjack actually uses a bitmask as opposed to primitive log levels.
|
||||
* This allows for a great amount of flexibility and some pretty advanced fine grained logging techniques.
|
||||
*
|
||||
* Note that when compiler optimizations are enabled (as they are for your release builds),
|
||||
* the log messages above your logging threshold will automatically be compiled out.
|
||||
*
|
||||
* (If the compiler sees ddLogLevel declared as a constant, the compiler simply checks to see if the 'if' statement
|
||||
* would execute, and if not it strips it from the binary.)
|
||||
*
|
||||
* We also define shorthand versions for asynchronous and synchronous logging.
|
||||
**/
|
||||
|
||||
#define LOG_MAYBE(async, lvl, flg, ctx, fnct, frmt, ...) \
|
||||
do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, nil, fnct, frmt, ##__VA_ARGS__); } while(0)
|
||||
|
||||
#define LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \
|
||||
LOG_MAYBE(async, lvl, flg, ctx, sel_getName(_cmd), frmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_C_MAYBE(async, lvl, flg, ctx, frmt, ...) \
|
||||
LOG_MAYBE(async, lvl, flg, ctx, __FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define SYNC_LOG_OBJC_MAYBE(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_OBJC_MAYBE( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define ASYNC_LOG_OBJC_MAYBE(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_OBJC_MAYBE(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define SYNC_LOG_C_MAYBE(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_C_MAYBE( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define ASYNC_LOG_C_MAYBE(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_C_MAYBE(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Define versions of the macros that also accept tags.
|
||||
*
|
||||
* The DDLogMessage object includes a 'tag' ivar that may be used for a variety of purposes.
|
||||
* It may be used to pass custom information to loggers or formatters.
|
||||
* Or it may be used by 3rd party extensions to the framework.
|
||||
*
|
||||
* Thes macros just make it a little easier to extend logging functionality.
|
||||
**/
|
||||
|
||||
#define LOG_OBJC_TAG_MACRO(async, lvl, flg, ctx, tag, frmt, ...) \
|
||||
LOG_MACRO(async, lvl, flg, ctx, tag, sel_getName(_cmd), frmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_C_TAG_MACRO(async, lvl, flg, ctx, tag, frmt, ...) \
|
||||
LOG_MACRO(async, lvl, flg, ctx, tag, __FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_TAG_MAYBE(async, lvl, flg, ctx, tag, fnct, frmt, ...) \
|
||||
do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, tag, fnct, frmt, ##__VA_ARGS__); } while(0)
|
||||
|
||||
#define LOG_OBJC_TAG_MAYBE(async, lvl, flg, ctx, tag, frmt, ...) \
|
||||
LOG_TAG_MAYBE(async, lvl, flg, ctx, tag, sel_getName(_cmd), frmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_C_TAG_MAYBE(async, lvl, flg, ctx, tag, frmt, ...) \
|
||||
LOG_TAG_MAYBE(async, lvl, flg, ctx, tag, __FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Define the standard options.
|
||||
*
|
||||
* We default to only 4 levels because it makes it easier for beginners
|
||||
* to make the transition to a logging framework.
|
||||
*
|
||||
* More advanced users may choose to completely customize the levels (and level names) to suite their needs.
|
||||
* For more information on this see the "Custom Log Levels" page:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomLogLevels
|
||||
*
|
||||
* Advanced users may also notice that we're using a bitmask.
|
||||
* This is to allow for custom fine grained logging:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/FineGrainedLogging
|
||||
*
|
||||
* -- Flags --
|
||||
*
|
||||
* Typically you will use the LOG_LEVELS (see below), but the flags may be used directly in certain situations.
|
||||
* For example, say you have a lot of warning log messages, and you wanted to disable them.
|
||||
* However, you still needed to see your error and info log messages.
|
||||
* You could accomplish that with the following:
|
||||
*
|
||||
* static const int ddLogLevel = LOG_FLAG_ERROR | LOG_FLAG_INFO;
|
||||
*
|
||||
* Flags may also be consulted when writing custom log formatters,
|
||||
* as the DDLogMessage class captures the individual flag that caused the log message to fire.
|
||||
*
|
||||
* -- Levels --
|
||||
*
|
||||
* Log levels are simply the proper bitmask of the flags.
|
||||
*
|
||||
* -- Booleans --
|
||||
*
|
||||
* The booleans may be used when your logging code involves more than one line.
|
||||
* For example:
|
||||
*
|
||||
* if (LOG_VERBOSE) {
|
||||
* for (id sprocket in sprockets)
|
||||
* DDLogVerbose(@"sprocket: %@", [sprocket description])
|
||||
* }
|
||||
*
|
||||
* -- Async --
|
||||
*
|
||||
* Defines the default asynchronous options.
|
||||
* The default philosophy for asynchronous logging is very simple:
|
||||
*
|
||||
* Log messages with errors should be executed synchronously.
|
||||
* After all, an error just occurred. The application could be unstable.
|
||||
*
|
||||
* All other log messages, such as debug output, are executed asynchronously.
|
||||
* After all, if it wasn't an error, then it was just informational output,
|
||||
* or something the application was easily able to recover from.
|
||||
*
|
||||
* -- Changes --
|
||||
*
|
||||
* You are strongly discouraged from modifying this file.
|
||||
* If you do, you make it more difficult on yourself to merge future bug fixes and improvements from the project.
|
||||
* Instead, create your own MyLogging.h or ApplicationNameLogging.h or CompanyLogging.h
|
||||
*
|
||||
* For an example of customizing your logging experience, see the "Custom Log Levels" page:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomLogLevels
|
||||
**/
|
||||
|
||||
#define LOG_FLAG_ERROR (1 << 0) // 0...0001
|
||||
#define LOG_FLAG_WARN (1 << 1) // 0...0010
|
||||
#define LOG_FLAG_INFO (1 << 2) // 0...0100
|
||||
#define LOG_FLAG_VERBOSE (1 << 3) // 0...1000
|
||||
|
||||
#define LOG_LEVEL_OFF 0
|
||||
#define LOG_LEVEL_ERROR (LOG_FLAG_ERROR) // 0...0001
|
||||
#define LOG_LEVEL_WARN (LOG_FLAG_ERROR | LOG_FLAG_WARN) // 0...0011
|
||||
#define LOG_LEVEL_INFO (LOG_FLAG_ERROR | LOG_FLAG_WARN | LOG_FLAG_INFO) // 0...0111
|
||||
#define LOG_LEVEL_VERBOSE (LOG_FLAG_ERROR | LOG_FLAG_WARN | LOG_FLAG_INFO | LOG_FLAG_VERBOSE) // 0...1111
|
||||
|
||||
#define LOG_ERROR (ddLogLevel & LOG_FLAG_ERROR)
|
||||
#define LOG_WARN (ddLogLevel & LOG_FLAG_WARN)
|
||||
#define LOG_INFO (ddLogLevel & LOG_FLAG_INFO)
|
||||
#define LOG_VERBOSE (ddLogLevel & LOG_FLAG_VERBOSE)
|
||||
|
||||
#define LOG_ASYNC_ENABLED YES
|
||||
|
||||
#define LOG_ASYNC_ERROR ( NO && LOG_ASYNC_ENABLED)
|
||||
#define LOG_ASYNC_WARN (YES && LOG_ASYNC_ENABLED)
|
||||
#define LOG_ASYNC_INFO (YES && LOG_ASYNC_ENABLED)
|
||||
#define LOG_ASYNC_VERBOSE (YES && LOG_ASYNC_ENABLED)
|
||||
|
||||
#define DDLogError(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_ERROR, ddLogLevel, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogWarn(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_WARN, ddLogLevel, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogInfo(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_INFO, ddLogLevel, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogVerbose(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_VERBOSE, ddLogLevel, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define DDLogCError(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_ERROR, ddLogLevel, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogCWarn(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_WARN, ddLogLevel, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogCInfo(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_INFO, ddLogLevel, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogCVerbose(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_VERBOSE, ddLogLevel, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* The THIS_FILE macro gives you an NSString of the file name.
|
||||
* For simplicity and clarity, the file name does not include the full path or file extension.
|
||||
*
|
||||
* For example: DDLogWarn(@"%@: Unable to find thingy", THIS_FILE) -> @"MyViewController: Unable to find thingy"
|
||||
**/
|
||||
|
||||
NSString *DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy);
|
||||
|
||||
#define THIS_FILE (DDExtractFileNameWithoutExtension(__FILE__, NO))
|
||||
|
||||
/**
|
||||
* The THIS_METHOD macro gives you the name of the current objective-c method.
|
||||
*
|
||||
* For example: DDLogWarn(@"%@ - Requires non-nil strings") -> @"setMake:model: requires non-nil strings"
|
||||
*
|
||||
* Note: This does NOT work in straight C functions (non objective-c).
|
||||
* Instead you should use the predefined __FUNCTION__ macro.
|
||||
**/
|
||||
|
||||
#define THIS_METHOD NSStringFromSelector(_cmd)
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface DDLog : NSObject
|
||||
|
||||
/**
|
||||
* Provides access to the underlying logging queue.
|
||||
* This may be helpful to Logger classes for things like thread synchronization.
|
||||
**/
|
||||
|
||||
+ (dispatch_queue_t)loggingQueue;
|
||||
|
||||
/**
|
||||
* Logging Primitive.
|
||||
*
|
||||
* This method is used by the macros above.
|
||||
* It is suggested you stick with the macros as they're easier to use.
|
||||
**/
|
||||
|
||||
+ (void)log:(BOOL)synchronous
|
||||
level:(int)level
|
||||
flag:(int)flag
|
||||
context:(int)context
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(int)line
|
||||
tag:(id)tag
|
||||
format:(NSString *)format, ... __attribute__ ((format (__NSString__, 9, 10)));
|
||||
|
||||
/**
|
||||
* Logging Primitive.
|
||||
*
|
||||
* This method can be used if you have a prepared va_list.
|
||||
**/
|
||||
|
||||
+ (void)log:(BOOL)asynchronous
|
||||
level:(int)level
|
||||
flag:(int)flag
|
||||
context:(int)context
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(int)line
|
||||
tag:(id)tag
|
||||
format:(NSString *)format
|
||||
args:(va_list)argList;
|
||||
|
||||
|
||||
/**
|
||||
* Since logging can be asynchronous, there may be times when you want to flush the logs.
|
||||
* The framework invokes this automatically when the application quits.
|
||||
**/
|
||||
|
||||
+ (void)flushLog;
|
||||
|
||||
/**
|
||||
* Loggers
|
||||
*
|
||||
* If you want your log statements to go somewhere,
|
||||
* you should create and add a logger.
|
||||
**/
|
||||
|
||||
+ (void)addLogger:(id <DDLogger>)logger;
|
||||
+ (void)removeLogger:(id <DDLogger>)logger;
|
||||
|
||||
+ (void)removeAllLoggers;
|
||||
|
||||
/**
|
||||
* Registered Dynamic Logging
|
||||
*
|
||||
* These methods allow you to obtain a list of classes that are using registered dynamic logging,
|
||||
* and also provides methods to get and set their log level during run time.
|
||||
**/
|
||||
|
||||
+ (NSArray *)registeredClasses;
|
||||
+ (NSArray *)registeredClassNames;
|
||||
|
||||
+ (int)logLevelForClass:(Class)aClass;
|
||||
+ (int)logLevelForClassWithName:(NSString *)aClassName;
|
||||
|
||||
+ (void)setLogLevel:(int)logLevel forClass:(Class)aClass;
|
||||
+ (void)setLogLevel:(int)logLevel forClassWithName:(NSString *)aClassName;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol DDLogger <NSObject>
|
||||
@required
|
||||
|
||||
- (void)logMessage:(DDLogMessage *)logMessage;
|
||||
|
||||
/**
|
||||
* Formatters may optionally be added to any logger.
|
||||
*
|
||||
* If no formatter is set, the logger simply logs the message as it is given in logMessage,
|
||||
* or it may use its own built in formatting style.
|
||||
**/
|
||||
- (id <DDLogFormatter>)logFormatter;
|
||||
- (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Since logging is asynchronous, adding and removing loggers is also asynchronous.
|
||||
* In other words, the loggers are added and removed at appropriate times with regards to log messages.
|
||||
*
|
||||
* - Loggers will not receive log messages that were executed prior to when they were added.
|
||||
* - Loggers will not receive log messages that were executed after they were removed.
|
||||
*
|
||||
* These methods are executed in the logging thread/queue.
|
||||
* This is the same thread/queue that will execute every logMessage: invocation.
|
||||
* Loggers may use these methods for thread synchronization or other setup/teardown tasks.
|
||||
**/
|
||||
- (void)didAddLogger;
|
||||
- (void)willRemoveLogger;
|
||||
|
||||
/**
|
||||
* Some loggers may buffer IO for optimization purposes.
|
||||
* For example, a database logger may only save occasionaly as the disk IO is slow.
|
||||
* In such loggers, this method should be implemented to flush any pending IO.
|
||||
*
|
||||
* This allows invocations of DDLog's flushLog method to be propogated to loggers that need it.
|
||||
*
|
||||
* Note that DDLog's flushLog method is invoked automatically when the application quits,
|
||||
* and it may be also invoked manually by the developer prior to application crashes, or other such reasons.
|
||||
**/
|
||||
- (void)flush;
|
||||
|
||||
/**
|
||||
* Each logger is executed concurrently with respect to the other loggers.
|
||||
* Thus, a dedicated dispatch queue is used for each logger.
|
||||
* Logger implementations may optionally choose to provide their own dispatch queue.
|
||||
**/
|
||||
- (dispatch_queue_t)loggerQueue;
|
||||
|
||||
/**
|
||||
* If the logger implementation does not choose to provide its own queue,
|
||||
* one will automatically be created for it.
|
||||
* The created queue will receive its name from this method.
|
||||
* This may be helpful for debugging or profiling reasons.
|
||||
**/
|
||||
- (NSString *)loggerName;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol DDLogFormatter <NSObject>
|
||||
@required
|
||||
|
||||
/**
|
||||
* Formatters may optionally be added to any logger.
|
||||
* This allows for increased flexibility in the logging environment.
|
||||
* For example, log messages for log files may be formatted differently than log messages for the console.
|
||||
*
|
||||
* For more information about formatters, see the "Custom Formatters" page:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters
|
||||
*
|
||||
* The formatter may also optionally filter the log message by returning nil,
|
||||
* in which case the logger will not log the message.
|
||||
**/
|
||||
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* A single formatter instance can be added to multiple loggers.
|
||||
* These methods provides hooks to notify the formatter of when it's added/removed.
|
||||
*
|
||||
* This is primarily for thread-safety.
|
||||
* If a formatter is explicitly not thread-safe, it may wish to throw an exception if added to multiple loggers.
|
||||
* Or if a formatter has potentially thread-unsafe code (e.g. NSDateFormatter),
|
||||
* it could possibly use these hooks to switch to thread-safe versions of the code.
|
||||
**/
|
||||
- (void)didAddToLogger:(id <DDLogger>)logger;
|
||||
- (void)willRemoveFromLogger:(id <DDLogger>)logger;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol DDRegisteredDynamicLogging
|
||||
|
||||
/**
|
||||
* Implement these methods to allow a file's log level to be managed from a central location.
|
||||
*
|
||||
* This is useful if you'd like to be able to change log levels for various parts
|
||||
* of your code from within the running application.
|
||||
*
|
||||
* Imagine pulling up the settings for your application,
|
||||
* and being able to configure the logging level on a per file basis.
|
||||
*
|
||||
* The implementation can be very straight-forward:
|
||||
*
|
||||
* + (int)ddLogLevel
|
||||
* {
|
||||
* return ddLogLevel;
|
||||
* }
|
||||
*
|
||||
* + (void)ddSetLogLevel:(int)logLevel
|
||||
* {
|
||||
* ddLogLevel = logLevel;
|
||||
* }
|
||||
**/
|
||||
|
||||
+ (int)ddLogLevel;
|
||||
+ (void)ddSetLogLevel:(int)logLevel;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The DDLogMessage class encapsulates information about the log message.
|
||||
* If you write custom loggers or formatters, you will be dealing with objects of this class.
|
||||
**/
|
||||
|
||||
enum {
|
||||
DDLogMessageCopyFile = 1 << 0,
|
||||
DDLogMessageCopyFunction = 1 << 1,
|
||||
};
|
||||
typedef int DDLogMessageOptions;
|
||||
|
||||
@interface DDLogMessage : NSObject
|
||||
{
|
||||
|
||||
// The public variables below can be accessed directly (for speed).
|
||||
// For example: logMessage->logLevel
|
||||
|
||||
@public
|
||||
int logLevel;
|
||||
int logFlag;
|
||||
int logContext;
|
||||
NSString *logMsg;
|
||||
NSDate *timestamp;
|
||||
char *file;
|
||||
char *function;
|
||||
int lineNumber;
|
||||
mach_port_t machThreadID;
|
||||
char *queueLabel;
|
||||
NSString *threadName;
|
||||
|
||||
// For 3rd party extensions to the framework, where flags and contexts aren't enough.
|
||||
id tag;
|
||||
|
||||
// For 3rd party extensions that manually create DDLogMessage instances.
|
||||
DDLogMessageOptions options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard init method for a log message object.
|
||||
* Used by the logging primitives. (And the macros use the logging primitives.)
|
||||
*
|
||||
* If you find need to manually create logMessage objects, there is one thing you should be aware of:
|
||||
*
|
||||
* If no flags are passed, the method expects the file and function parameters to be string literals.
|
||||
* That is, it expects the given strings to exist for the duration of the object's lifetime,
|
||||
* and it expects the given strings to be immutable.
|
||||
* In other words, it does not copy these strings, it simply points to them.
|
||||
* This is due to the fact that __FILE__ and __FUNCTION__ are usually used to specify these parameters,
|
||||
* so it makes sense to optimize and skip the unnecessary allocations.
|
||||
* However, if you need them to be copied you may use the options parameter to specify this.
|
||||
* Options is a bitmask which supports DDLogMessageCopyFile and DDLogMessageCopyFunction.
|
||||
**/
|
||||
- (id)initWithLogMsg:(NSString *)logMsg
|
||||
level:(int)logLevel
|
||||
flag:(int)logFlag
|
||||
context:(int)logContext
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(int)line
|
||||
tag:(id)tag
|
||||
options:(DDLogMessageOptions)optionsMask;
|
||||
|
||||
/**
|
||||
* Returns the threadID as it appears in NSLog.
|
||||
* That is, it is a hexadecimal value which is calculated from the machThreadID.
|
||||
**/
|
||||
- (NSString *)threadID;
|
||||
|
||||
/**
|
||||
* Convenience property to get just the file name, as the file variable is generally the full file path.
|
||||
* This method does not include the file extension, which is generally unwanted for logging purposes.
|
||||
**/
|
||||
- (NSString *)fileName;
|
||||
|
||||
/**
|
||||
* Returns the function variable in NSString form.
|
||||
**/
|
||||
- (NSString *)methodName;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The DDLogger protocol specifies that an optional formatter can be added to a logger.
|
||||
* Most (but not all) loggers will want to support formatters.
|
||||
*
|
||||
* However, writting getters and setters in a thread safe manner,
|
||||
* while still maintaining maximum speed for the logging process, is a difficult task.
|
||||
*
|
||||
* To do it right, the implementation of the getter/setter has strict requiremenets:
|
||||
* - Must NOT require the logMessage method to acquire a lock.
|
||||
* - Must NOT require the logMessage method to access an atomic property (also a lock of sorts).
|
||||
*
|
||||
* To simplify things, an abstract logger is provided that implements the getter and setter.
|
||||
*
|
||||
* Logger implementations may simply extend this class,
|
||||
* and they can ACCESS THE FORMATTER VARIABLE DIRECTLY from within their logMessage method!
|
||||
**/
|
||||
|
||||
@interface DDAbstractLogger : NSObject <DDLogger>
|
||||
{
|
||||
id <DDLogFormatter> formatter;
|
||||
|
||||
dispatch_queue_t loggerQueue;
|
||||
}
|
||||
|
||||
- (id <DDLogFormatter>)logFormatter;
|
||||
- (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,167 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#if TARGET_OS_IPHONE
|
||||
#import <UIKit/UIColor.h>
|
||||
#else
|
||||
#import <AppKit/NSColor.h>
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
*
|
||||
*
|
||||
* This class provides a logger for Terminal output or Xcode console output,
|
||||
* depending on where you are running your code.
|
||||
*
|
||||
* As described in the "Getting Started" page,
|
||||
* the traditional NSLog() function directs it's output to two places:
|
||||
*
|
||||
* - Apple System Log (so it shows up in Console.app)
|
||||
* - StdErr (if stderr is a TTY, so log statements show up in Xcode console)
|
||||
*
|
||||
* To duplicate NSLog() functionality you can simply add this logger and an asl logger.
|
||||
* However, if you instead choose to use file logging (for faster performance),
|
||||
* you may choose to use only a file logger and a tty logger.
|
||||
**/
|
||||
|
||||
@interface DDTTYLogger : DDAbstractLogger <DDLogger>
|
||||
{
|
||||
NSCalendar *calendar;
|
||||
NSUInteger calendarUnitFlags;
|
||||
|
||||
NSString *appName;
|
||||
char *app;
|
||||
size_t appLen;
|
||||
|
||||
NSString *processID;
|
||||
char *pid;
|
||||
size_t pidLen;
|
||||
|
||||
BOOL colorsEnabled;
|
||||
NSMutableArray *colorProfilesArray;
|
||||
NSMutableDictionary *colorProfilesDict;
|
||||
}
|
||||
|
||||
+ (DDTTYLogger *)sharedInstance;
|
||||
|
||||
/* Inherited from the DDLogger protocol:
|
||||
*
|
||||
* Formatters may optionally be added to any logger.
|
||||
*
|
||||
* If no formatter is set, the logger simply logs the message as it is given in logMessage,
|
||||
* or it may use its own built in formatting style.
|
||||
*
|
||||
* More information about formatters can be found here:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters
|
||||
*
|
||||
* The actual implementation of these methods is inherited from DDAbstractLogger.
|
||||
|
||||
- (id <DDLogFormatter>)logFormatter;
|
||||
- (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Want to use different colors for different log levels?
|
||||
* Enable this property.
|
||||
*
|
||||
* If you run the application via the Terminal (not Xcode),
|
||||
* the logger will map colors to xterm-256color or xterm-color (if available).
|
||||
*
|
||||
* Xcode does NOT natively support colors in the Xcode debugging console.
|
||||
* You'll need to install the XcodeColors plugin to see colors in the Xcode console.
|
||||
* https://github.com/robbiehanson/XcodeColors
|
||||
*
|
||||
* The default value if NO.
|
||||
**/
|
||||
@property (readwrite, assign) BOOL colorsEnabled;
|
||||
|
||||
/**
|
||||
* The default color set (foregroundColor, backgroundColor) is:
|
||||
*
|
||||
* - LOG_FLAG_ERROR = (red, nil)
|
||||
* - LOG_FLAG_WARN = (orange, nil)
|
||||
*
|
||||
* You can customize the colors however you see fit.
|
||||
* Please note that you are passing a flag, NOT a level.
|
||||
*
|
||||
* GOOD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:LOG_FLAG_INFO]; // <- Good :)
|
||||
* BAD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:LOG_LEVEL_INFO]; // <- BAD! :(
|
||||
*
|
||||
* LOG_FLAG_INFO = 0...00100
|
||||
* LOG_LEVEL_INFO = 0...00111 <- Would match LOG_FLAG_INFO and LOG_FLAG_WARN and LOG_FLAG_ERROR
|
||||
*
|
||||
* If you run the application within Xcode, then the XcodeColors plugin is required.
|
||||
*
|
||||
* If you run the application from a shell, then DDTTYLogger will automatically map the given color to
|
||||
* the closest available color. (xterm-256color or xterm-color which have 256 and 16 supported colors respectively.)
|
||||
*
|
||||
* This method invokes setForegroundColor:backgroundColor:forFlag:context: and passes the default context (0).
|
||||
**/
|
||||
#if TARGET_OS_IPHONE
|
||||
- (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forFlag:(int)mask;
|
||||
#else
|
||||
- (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forFlag:(int)mask;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Just like setForegroundColor:backgroundColor:flag, but allows you to specify a particular logging context.
|
||||
*
|
||||
* A logging context is often used to identify log messages coming from a 3rd party framework,
|
||||
* although logging context's can be used for many different functions.
|
||||
*
|
||||
* Logging context's are explained in further detail here:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomContext
|
||||
**/
|
||||
#if TARGET_OS_IPHONE
|
||||
- (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forFlag:(int)mask context:(int)ctxt;
|
||||
#else
|
||||
- (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forFlag:(int)mask context:(int)ctxt;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Similar to the methods above, but allows you to map DDLogMessage->tag to a particular color profile.
|
||||
* For example, you could do something like this:
|
||||
*
|
||||
* static NSString *const PurpleTag = @"PurpleTag";
|
||||
*
|
||||
* #define DDLogPurple(frmt, ...) LOG_OBJC_TAG_MACRO(NO, 0, 0, 0, PurpleTag, frmt, ##__VA_ARGS__)
|
||||
*
|
||||
* And then in your applicationDidFinishLaunching, or wherever you configure Lumberjack:
|
||||
*
|
||||
* #if TARGET_OS_IPHONE
|
||||
* UIColor *purple = [UIColor colorWithRed:(64/255.0) green:(0/255.0) blue:(128/255.0) alpha:1.0];
|
||||
* #else
|
||||
* NSColor *purple = [NSColor colorWithCalibratedRed:(64/255.0) green:(0/255.0) blue:(128/255.0) alpha:1.0];
|
||||
*
|
||||
* [[DDTTYLogger sharedInstance] setForegroundColor:purple backgroundColor:nil forTag:PurpleTag];
|
||||
* [DDLog addLogger:[DDTTYLogger sharedInstance]];
|
||||
*
|
||||
* This would essentially give you a straight NSLog replacement that prints in purple:
|
||||
*
|
||||
* DDLogPurple(@"I'm a purple log message!");
|
||||
**/
|
||||
#if TARGET_OS_IPHONE
|
||||
- (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forTag:(id <NSCopying>)tag;
|
||||
#else
|
||||
- (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forTag:(id <NSCopying>)tag;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Clearing color profiles.
|
||||
**/
|
||||
- (void)clearColorsForFlag:(int)mask;
|
||||
- (void)clearColorsForFlag:(int)mask context:(int)context;
|
||||
- (void)clearColorsForTag:(id <NSCopying>)tag;
|
||||
- (void)clearColorsForAllFlags;
|
||||
- (void)clearColorsForAllTags;
|
||||
- (void)clearAllColors;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Notifications.h
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/27/13.
|
||||
// Copyright (c) 2013 Cappuccino Project. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef XcodeCapp_Notifications_h
|
||||
#define XcodeCapp_Notifications_h
|
||||
|
||||
extern NSString * const XCCProjectDidFinishLoadingNotification;
|
||||
extern NSString * const XCCBatchDidStartNotification;
|
||||
extern NSString * const XCCBatchDidEndNotification;
|
||||
extern NSString * const XCCConversionDidStartNotification;
|
||||
extern NSString * const XCCConversionDidEndNotification;
|
||||
extern NSString * const XCCConversionDidGenerateErrorNotification;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Notifications.m
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/27/13.
|
||||
//
|
||||
//
|
||||
|
||||
#include "Notifications.h"
|
||||
|
||||
|
||||
NSString * const XCCProjectDidFinishLoadingNotification = @"XCCProjectDidFinishLoadingNotification";
|
||||
NSString * const XCCBatchDidStartNotification = @"XCCBatchDidStartNotification";
|
||||
NSString * const XCCBatchDidEndNotification = @"XCCBatchDidEndNotification";
|
||||
NSString * const XCCConversionDidStartNotification = @"XCCConversionDidStartNotification";
|
||||
NSString * const XCCConversionDidEndNotification = @"XCCConversionDidStopNotification";
|
||||
NSString * const XCCConversionDidGenerateErrorNotification = @"XCCConversionDidGenerateErrorNotification";
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// ProcessSourceOperation.h
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/27/13.
|
||||
// Copyright (c) 2013 Cappuccino Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class XcodeCapp;
|
||||
|
||||
|
||||
@interface ProcessSourceOperation : NSOperation
|
||||
|
||||
// sourcePath should be a path within the project (no resolved symlinks)
|
||||
- (id)initWithXCC:(XcodeCapp *)xcc projectId:(NSNumber *)projectId sourcePath:(NSString *)sourcePath;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,184 @@
|
||||
//
|
||||
// ProcessSourceOperation.m
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/27/13.
|
||||
// Copyright (c) 2013 Cappuccino Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ProcessSourceOperation.h"
|
||||
#import "Notifications.h"
|
||||
#import "XcodeCapp.h"
|
||||
|
||||
|
||||
@interface ProcessSourceOperation ()
|
||||
|
||||
@property XcodeCapp *xcc;
|
||||
@property NSNumber *projectId;
|
||||
@property NSString *sourcePath;
|
||||
@property NSString *projectPath;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ProcessSourceOperation
|
||||
|
||||
- (id)initWithXCC:(XcodeCapp *)xcc projectId:(NSNumber *)projectId sourcePath:(NSString *)sourcePath
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
self.xcc = xcc;
|
||||
self.projectId = projectId;
|
||||
self.sourcePath = sourcePath;
|
||||
self.projectPath = xcc.projectPath;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)main
|
||||
{
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
|
||||
NSDictionary *info = @{ @"projectId":self.projectId, @"path":self.sourcePath };
|
||||
[center postNotificationName:XCCConversionDidStartNotification object:self userInfo:info];
|
||||
|
||||
DDLogVerbose(@"Conversion started: %@", self.sourcePath);
|
||||
|
||||
NSString *launchPath = nil;
|
||||
NSArray *arguments = nil;
|
||||
NSString *response = nil;
|
||||
NSString *projectRelativePath = [self.sourcePath substringFromIndex:self.projectPath.length + 1];
|
||||
NSString *notificationTitle = nil;
|
||||
NSString *notificationMessage = projectRelativePath.lastPathComponent;
|
||||
|
||||
if ([self.xcc isXibFile:self.sourcePath])
|
||||
{
|
||||
launchPath = self.xcc.executablePaths[@"nib2cib"];
|
||||
arguments = @[
|
||||
@"--no-colors",
|
||||
self.sourcePath
|
||||
];
|
||||
|
||||
notificationTitle = @"Xib converted";
|
||||
}
|
||||
else if ([self.xcc isObjjFile:self.sourcePath])
|
||||
{
|
||||
launchPath = self.xcc.executablePaths[@"objj"];
|
||||
arguments = @[
|
||||
self.xcc.parserPath,
|
||||
self.projectPath,
|
||||
self.sourcePath
|
||||
];
|
||||
|
||||
notificationTitle = @"Objective-J source processed";
|
||||
}
|
||||
else if ([self.xcc isXCCIgnoreFile:self.sourcePath])
|
||||
{
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
[self.xcc performSelectorOnMainThread:@selector(computeIgnoredPaths) withObject:nil waitUntilDone:NO];
|
||||
|
||||
notificationTitle = @"Parsed .xcodecapp-ignore";
|
||||
notificationMessage = @"Ignored paths updated";
|
||||
}
|
||||
|
||||
// Run the task and get the response if needed
|
||||
NSInteger status = 0;
|
||||
|
||||
if (arguments)
|
||||
{
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
DDLogVerbose(@"Running processing task: %@", launchPath);
|
||||
|
||||
NSDictionary *taskResult = [self.xcc runTaskWithLaunchPath:launchPath
|
||||
arguments:arguments
|
||||
returnType:kTaskReturnTypeStdError];
|
||||
|
||||
status = [taskResult[@"status"] intValue];
|
||||
response = taskResult[@"response"];
|
||||
|
||||
DDLogInfo(@"Processed %@: [%ld, %@]", self.sourcePath, status, status ? response : @"");
|
||||
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
if (status != 0)
|
||||
{
|
||||
if (response.length == 0)
|
||||
response = @"An unspecified error occurred";
|
||||
|
||||
if ([self.xcc isXibFile:self.sourcePath])
|
||||
{
|
||||
notificationTitle = @"Error converting xib";
|
||||
NSString *message = [NSString stringWithFormat:@"%@\n%@", self.sourcePath.lastPathComponent, response];
|
||||
|
||||
NSDictionary *info =
|
||||
@{
|
||||
@"projectId":self.projectId,
|
||||
@"message":message,
|
||||
@"path":self.sourcePath,
|
||||
@"status":taskResult[@"status"]
|
||||
};
|
||||
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
[center postNotificationName:XCCConversionDidGenerateErrorNotification object:self userInfo:info];
|
||||
}
|
||||
else
|
||||
{
|
||||
notificationTitle = [(status == XCCStatusCodeError ? @"Error" : @"Warning") stringByAppendingString:@" parsing Objective-J source"];
|
||||
|
||||
NSArray *errors = [response propertyList];
|
||||
|
||||
for (NSDictionary *error in errors)
|
||||
{
|
||||
NSMutableDictionary *info = [error mutableCopy];
|
||||
info[@"projectId"] = self.projectId;
|
||||
info[@"message"] = [NSString stringWithFormat:@"%@, line %d\n%@", [error[@"path"] lastPathComponent], [error[@"line"] intValue], error[@"message"]];
|
||||
info[@"status"] = taskResult[@"status"];
|
||||
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
[center postNotificationName:XCCConversionDidGenerateErrorNotification object:self userInfo:info];
|
||||
}
|
||||
}
|
||||
|
||||
[self notifyUserWithTitle:notificationTitle message:notificationMessage];
|
||||
}
|
||||
else if (!self.xcc.isLoadingProject)
|
||||
{
|
||||
[self notifyUserWithTitle:notificationTitle message:notificationMessage];
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.isCancelled)
|
||||
{
|
||||
DDLogVerbose(@"Conversion ended: %@", self.sourcePath);
|
||||
|
||||
[center postNotificationName:XCCConversionDidEndNotification object:self userInfo:@{ @"projectId":self.projectId, @"path":self.sourcePath }];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyUserWithTitle:(NSString *)title message:(NSString *)message
|
||||
{
|
||||
NSDictionary *info = @{ @"projectId":self.projectId, @"title":title, @"message":message };
|
||||
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
// nib2cib can take a while to run, show a message while the conversion is happening
|
||||
[self.xcc wantUserNotificationWithInfo:info];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Release.xcconfig
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/18/13.
|
||||
//
|
||||
//
|
||||
|
||||
//:configuration = Release
|
||||
ARCHS = $(ARCHS_STANDARD_64_BIT)
|
||||
SDKROOT = macosx
|
||||
ONLY_ACTIVE_ARCH = YES
|
||||
DEBUG_INFORMATION_FORMAT = dwarf
|
||||
COMBINE_HIDPI_IMAGES = YES
|
||||
INSTALL_PATH = $(LOCAL_APPS_DIR)
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.6.8
|
||||
COPY_PHASE_STRIP = YES
|
||||
INFOPLIST_FILE = XcodeCapp/Info.plist
|
||||
PRODUCT_NAME = XcodeCapp
|
||||
ALWAYS_SEARCH_USER_PATHS = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(SRCROOT)/XcodeCapp"
|
||||
GCC_OPTIMIZATION_LEVEL = s
|
||||
CLANG_ENABLE_OBJC_ARC = YES
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES
|
||||
GCC_PREFIX_HEADER = XcodeCapp/XcodeCapp-Prefix.pch
|
||||
GCC_PREPROCESSOR_DEFINITIONS =
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES
|
||||
GCC_WARN_UNUSED_VARIABLE = YES
|
||||
WRAPPER_EXTENSION = app
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99
|
||||
CLANG_CXX_LANGUAGE_STANDARD = gnu++0x
|
||||
CLANG_CXX_LIBRARY = libc++
|
||||
CLANG_WARN_EMPTY_BODY = YES
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES
|
||||
CLANG_WARN_ENUM_CONVERSION = YES
|
||||
CLANG_WARN_INT_CONVERSION = YES
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES
|
||||
GCC_WARN_UNUSED_VARIABLE = YES
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 874 B |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 943 KiB |
|
After Width: | Height: | Size: 619 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 713 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 639 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 719 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 83 KiB |
@@ -0,0 +1,72 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
E164FE29172185F500263CE3 /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
E164FE221721857400263CE3 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E164FE29172185F500263CE3 /* Resources */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
E164FE231721857400263CE3 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0460;
|
||||
};
|
||||
buildConfigurationList = E164FE261721857400263CE3 /* Build configuration list for PBXProject "Cappuccino" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = E164FE221721857400263CE3;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
E164FE271721857400263CE3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
E164FE281721857400263CE3 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
E164FE261721857400263CE3 /* Build configuration list for PBXProject "Cappuccino" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
E164FE271721857400263CE3 /* Debug */,
|
||||
E164FE281721857400263CE3 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = E164FE231721857400263CE3 /* Project object */;
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
# Copyright 2012 Calvin Rien
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# A pbxproj file is an OpenStep format plist
|
||||
# {} represents dictionary of key=value pairs delimited by ;
|
||||
@@ -32,11 +32,22 @@
|
||||
# the pbxproj file. Plutil is available in OS X 10.2 and higher
|
||||
# Plutil can't write OpenStep plists, so I save as XML
|
||||
|
||||
import re, uuid, sys, os, shutil, subprocess, datetime, json
|
||||
import datetime
|
||||
import json
|
||||
import ntpath
|
||||
import os
|
||||
import plistlib
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import uuid
|
||||
|
||||
from UserDict import IterableUserDict
|
||||
from UserList import UserList
|
||||
|
||||
regex = '[a-zA-Z0-9\\._/-]*'
|
||||
|
||||
|
||||
class PBXEncoder(json.JSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
@@ -51,7 +62,7 @@ class PBXEncoder(json.JSONEncoder):
|
||||
class PBXDict(IterableUserDict):
|
||||
def __init__(self, d=None):
|
||||
if d:
|
||||
d = dict([(PBXType.Convert(k),PBXType.Convert(v)) for k,v in d.items()])
|
||||
d = dict([(PBXType.Convert(k), PBXType.Convert(v)) for k, v in d.items()])
|
||||
|
||||
IterableUserDict.__init__(self, d)
|
||||
|
||||
@@ -62,7 +73,6 @@ class PBXDict(IterableUserDict):
|
||||
self.data.pop(PBXType.Convert(key), None)
|
||||
|
||||
|
||||
|
||||
class PBXList(UserList):
|
||||
def __init__(self, l=None):
|
||||
if isinstance(l, basestring):
|
||||
@@ -97,7 +107,7 @@ class PBXType(PBXDict):
|
||||
def __init__(self, d=None):
|
||||
PBXDict.__init__(self, d)
|
||||
|
||||
if not self.has_key('isa'):
|
||||
if 'isa' not in self:
|
||||
self['isa'] = self.__class__.__name__
|
||||
self.id = None
|
||||
|
||||
@@ -140,18 +150,20 @@ class PBXFileReference(PBXType):
|
||||
self.build_phase = None
|
||||
|
||||
types = {
|
||||
'.a':('archive.ar', 'PBXFrameworksBuildPhase'),
|
||||
'.a': ('archive.ar', 'PBXFrameworksBuildPhase'),
|
||||
'.app': ('wrapper.application', None),
|
||||
'.s': ('sourcecode.asm', 'PBXSourcesBuildPhase'),
|
||||
'.c': ('sourcecode.c.c', 'PBXSourcesBuildPhase'),
|
||||
'.cpp': ('sourcecode.cpp.cpp', 'PBXSourcesBuildPhase'),
|
||||
'.framework': ('wrapper.framework','PBXFrameworksBuildPhase'),
|
||||
'.framework': ('wrapper.framework', 'PBXFrameworksBuildPhase'),
|
||||
'.h': ('sourcecode.c.h', None),
|
||||
'.icns': ('image.icns','PBXResourcesBuildPhase'),
|
||||
'.icns': ('image.icns', 'PBXResourcesBuildPhase'),
|
||||
'.m': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'),
|
||||
'.j': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'),
|
||||
'.mm': ('sourcecode.cpp.objcpp', 'PBXSourcesBuildPhase'),
|
||||
'.nib': ('wrapper.nib', 'PBXResourcesBuildPhase'),
|
||||
'.plist': ('text.plist.xml', 'PBXResourcesBuildPhase'),
|
||||
'.json': ('text.json', 'PBXResourcesBuildPhase'),
|
||||
'.png': ('image.png', 'PBXResourcesBuildPhase'),
|
||||
'.rtf': ('text.rtf', 'PBXResourcesBuildPhase'),
|
||||
'.tiff': ('image.tiff', 'PBXResourcesBuildPhase'),
|
||||
@@ -172,17 +184,22 @@ class PBXFileReference(PBXType):
|
||||
'SOURCE_ROOT',
|
||||
]
|
||||
|
||||
def guess_file_type(self):
|
||||
def guess_file_type(self, ignore_unknown_type=True):
|
||||
self.remove('explicitFileType')
|
||||
self.remove('lastKnownFileType')
|
||||
ext = os.path.splitext(self.get('name', ''))[1]
|
||||
|
||||
f_type, build_phase = PBXFileReference.types.get(ext, ('?', None))
|
||||
if os.path.isdir(self.get('path')):
|
||||
f_type = 'folder'
|
||||
build_phase = None
|
||||
ext = ''
|
||||
else:
|
||||
ext = os.path.splitext(self.get('name', ''))[1]
|
||||
f_type, build_phase = PBXFileReference.types.get(ext, ('?', None))
|
||||
|
||||
self['lastKnownFileType'] = f_type
|
||||
self.build_phase = build_phase
|
||||
|
||||
if f_type == '?':
|
||||
if f_type == '?' and not ignore_unknown_type:
|
||||
print 'unknown file extension: %s' % ext
|
||||
print 'please add extension and Xcode type to PBXFileReference.types'
|
||||
|
||||
@@ -195,7 +212,7 @@ class PBXFileReference(PBXType):
|
||||
self['explicitFileType'] = ft
|
||||
|
||||
@classmethod
|
||||
def Create(cls, os_path, tree='SOURCE_ROOT'):
|
||||
def Create(cls, os_path, tree='SOURCE_ROOT', ignore_unknown_type=True):
|
||||
if tree not in cls.trees:
|
||||
print 'Not a valid sourceTree type: %s' % tree
|
||||
return None
|
||||
@@ -205,10 +222,11 @@ class PBXFileReference(PBXType):
|
||||
fr['path'] = os_path
|
||||
fr['name'] = os.path.split(os_path)[1]
|
||||
fr['sourceTree'] = '<absolute>' if os.path.isabs(os_path) else tree
|
||||
fr.guess_file_type()
|
||||
fr.guess_file_type(ignore_unknown_type=ignore_unknown_type)
|
||||
|
||||
return fr
|
||||
|
||||
|
||||
class PBXBuildFile(PBXType):
|
||||
def set_weak_link(self, weak=False):
|
||||
k_settings = 'settings'
|
||||
@@ -218,7 +236,7 @@ class PBXBuildFile(PBXType):
|
||||
|
||||
if not s:
|
||||
if weak:
|
||||
self[k_settings] = PBXDict({k_attributes:PBXList(['Weak'])})
|
||||
self[k_settings] = PBXDict({k_attributes: PBXList(['Weak'])})
|
||||
|
||||
return True
|
||||
|
||||
@@ -243,10 +261,10 @@ class PBXBuildFile(PBXType):
|
||||
k_settings = 'settings'
|
||||
k_attributes = 'COMPILER_FLAGS'
|
||||
|
||||
if not self.has_key(k_settings):
|
||||
if k_settings not in self:
|
||||
self[k_settings] = PBXDict()
|
||||
|
||||
if not self[k_settings].has_key(k_attributes):
|
||||
if k_attributes not in self[k_settings]:
|
||||
self[k_settings][k_attributes] = flag
|
||||
return True
|
||||
|
||||
@@ -273,6 +291,7 @@ class PBXBuildFile(PBXType):
|
||||
|
||||
return bf
|
||||
|
||||
|
||||
class PBXGroup(PBXType):
|
||||
def add_child(self, ref):
|
||||
if not isinstance(ref, PBXDict):
|
||||
@@ -283,7 +302,7 @@ class PBXGroup(PBXType):
|
||||
if isa != 'PBXFileReference' and isa != 'PBXGroup':
|
||||
return None
|
||||
|
||||
if not self.has_key('children'):
|
||||
if 'children' not in self:
|
||||
self['children'] = PBXList()
|
||||
|
||||
self['children'].add(ref.id)
|
||||
@@ -291,7 +310,7 @@ class PBXGroup(PBXType):
|
||||
return ref.id
|
||||
|
||||
def remove_child(self, id):
|
||||
if not self.has_key('children'):
|
||||
if 'children' not in self:
|
||||
self['children'] = PBXList()
|
||||
return
|
||||
|
||||
@@ -301,7 +320,7 @@ class PBXGroup(PBXType):
|
||||
self['children'].remove(id)
|
||||
|
||||
def has_child(self, id):
|
||||
if not self.has_key('children'):
|
||||
if 'children' not in self:
|
||||
self['children'] = PBXList()
|
||||
return False
|
||||
|
||||
@@ -311,7 +330,7 @@ class PBXGroup(PBXType):
|
||||
return id in self['children']
|
||||
|
||||
def get_name(self):
|
||||
path_name = os.path.split(self.get('path',''))[1]
|
||||
path_name = os.path.split(self.get('path', ''))[1]
|
||||
return self.get('name', path_name)
|
||||
|
||||
@classmethod
|
||||
@@ -350,12 +369,16 @@ class PBXVariantGroup(PBXType):
|
||||
pass
|
||||
|
||||
|
||||
class PBXTargetDependency(PBXType):
|
||||
pass
|
||||
|
||||
|
||||
class PBXBuildPhase(PBXType):
|
||||
def add_build_file(self, bf):
|
||||
if bf.get('isa') != 'PBXBuildFile':
|
||||
return False
|
||||
|
||||
if not self.has_key('files'):
|
||||
if 'files' not in self:
|
||||
self['files'] = PBXList()
|
||||
|
||||
self['files'].add(bf.id)
|
||||
@@ -363,14 +386,14 @@ class PBXBuildPhase(PBXType):
|
||||
return True
|
||||
|
||||
def remove_build_file(self, id):
|
||||
if not self.has_key('files'):
|
||||
if 'files' not in self:
|
||||
self['files'] = PBXList()
|
||||
return
|
||||
|
||||
self['files'].remove(id)
|
||||
|
||||
def has_build_file(self, id):
|
||||
if not self.has_key('files'):
|
||||
if 'files' not in self:
|
||||
self['files'] = PBXList()
|
||||
return False
|
||||
|
||||
@@ -401,26 +424,30 @@ class PBXCopyFilesBuildPhase(PBXBuildPhase):
|
||||
|
||||
|
||||
class XCBuildConfiguration(PBXType):
|
||||
def add_search_paths(self, paths, base, key, recursive=True):
|
||||
def add_search_paths(self, paths, base, key, recursive=True, escape=True):
|
||||
modified = False
|
||||
|
||||
if not isinstance(paths, list):
|
||||
paths = [paths]
|
||||
|
||||
if not self.has_key(base):
|
||||
if base not in self:
|
||||
self[base] = PBXDict()
|
||||
|
||||
for path in paths:
|
||||
if recursive and not path.endswith('/**'):
|
||||
path = os.path.join(path, '**')
|
||||
|
||||
if not self[base].has_key(key):
|
||||
if key not in self[base]:
|
||||
self[base][key] = PBXList()
|
||||
elif isinstance(self[base][key], basestring):
|
||||
self[base][key] = PBXList(self[base][key])
|
||||
|
||||
if self[base][key].add('\\"%s\\"' % path):
|
||||
modified = True
|
||||
if escape:
|
||||
if self[base][key].add('\\"%s\\"' % path): # '\\"%s\\"' % path
|
||||
modified = True
|
||||
else:
|
||||
if self[base][key].add(path): # '\\"%s\\"' % path
|
||||
modified = True
|
||||
|
||||
return modified
|
||||
|
||||
@@ -430,6 +457,9 @@ class XCBuildConfiguration(PBXType):
|
||||
def add_library_search_paths(self, paths, recursive=True):
|
||||
return self.add_search_paths(paths, 'buildSettings', 'LIBRARY_SEARCH_PATHS', recursive=recursive)
|
||||
|
||||
def add_framework_search_paths(self, paths, recursive=True):
|
||||
return self.add_search_paths(paths, 'buildSettings', 'FRAMEWORK_SEARCH_PATHS', recursive=recursive, escape=False)
|
||||
|
||||
def add_other_cflags(self, flags):
|
||||
modified = False
|
||||
|
||||
@@ -439,12 +469,11 @@ class XCBuildConfiguration(PBXType):
|
||||
if isinstance(flags, basestring):
|
||||
flags = PBXList(flags)
|
||||
|
||||
if not self.has_key(base):
|
||||
if base not in self:
|
||||
self[base] = PBXDict()
|
||||
|
||||
for flag in flags:
|
||||
|
||||
if not self[base].has_key(key):
|
||||
if key not in self[base]:
|
||||
self[base][key] = PBXList()
|
||||
elif isinstance(self[base][key], basestring):
|
||||
self[base][key] = PBXList(self[base][key])
|
||||
@@ -455,6 +484,52 @@ class XCBuildConfiguration(PBXType):
|
||||
|
||||
return modified
|
||||
|
||||
def add_other_ldflags(self, flags):
|
||||
modified = False
|
||||
|
||||
base = 'buildSettings'
|
||||
key = 'OTHER_LDFLAGS'
|
||||
|
||||
if isinstance(flags, basestring):
|
||||
flags = PBXList(flags)
|
||||
|
||||
if base not in self:
|
||||
self[base] = PBXDict()
|
||||
|
||||
for flag in flags:
|
||||
if key not in self[base]:
|
||||
self[base][key] = PBXList()
|
||||
elif isinstance(self[base][key], basestring):
|
||||
self[base][key] = PBXList(self[base][key])
|
||||
|
||||
if self[base][key].add(flag):
|
||||
self[base][key] = [e for e in self[base][key] if e]
|
||||
modified = True
|
||||
|
||||
return modified
|
||||
|
||||
def remove_other_ldflags(self, flags):
|
||||
modified = False
|
||||
|
||||
base = 'buildSettings'
|
||||
key = 'OTHER_LDFLAGS'
|
||||
|
||||
if isinstance(flags, basestring):
|
||||
flags = PBXList(flags)
|
||||
|
||||
if base in self: # there are flags, so we can "remove" something
|
||||
for flag in flags:
|
||||
if key not in self[base]:
|
||||
return False
|
||||
elif isinstance(self[base][key], basestring):
|
||||
self[base][key] = PBXList(self[base][key])
|
||||
|
||||
if self[base][key].remove(flag):
|
||||
self[base][key] = [e for e in self[base][key] if e]
|
||||
modified = True
|
||||
|
||||
return modified
|
||||
|
||||
|
||||
class XCConfigurationList(PBXType):
|
||||
pass
|
||||
@@ -468,7 +543,7 @@ class XcodeProject(PBXDict):
|
||||
if not path:
|
||||
path = os.path.join(os.getcwd(), 'project.pbxproj')
|
||||
|
||||
self.pbxproj_path =os.path.abspath(path)
|
||||
self.pbxproj_path = os.path.abspath(path)
|
||||
self.source_root = os.path.abspath(os.path.join(os.path.split(path)[0], '..'))
|
||||
|
||||
IterableUserDict.__init__(self, d)
|
||||
@@ -478,6 +553,7 @@ class XcodeProject(PBXDict):
|
||||
self.modified = False
|
||||
|
||||
root_id = self.get('rootObject')
|
||||
|
||||
if root_id:
|
||||
self.root_object = self.objects[root_id]
|
||||
root_group_id = self.root_object.get('mainGroup')
|
||||
@@ -487,7 +563,7 @@ class XcodeProject(PBXDict):
|
||||
self.root_object = None
|
||||
self.root_group = None
|
||||
|
||||
for k,v in self.objects.iteritems():
|
||||
for k, v in self.objects.iteritems():
|
||||
v.id = k
|
||||
|
||||
def add_other_cflags(self, flags):
|
||||
@@ -497,6 +573,20 @@ class XcodeProject(PBXDict):
|
||||
if b.add_other_cflags(flags):
|
||||
self.modified = True
|
||||
|
||||
def add_other_ldflags(self, flags):
|
||||
build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
|
||||
|
||||
for b in build_configs:
|
||||
if b.add_other_ldflags(flags):
|
||||
self.modified = True
|
||||
|
||||
def remove_other_ldflags(self, flags):
|
||||
build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
|
||||
|
||||
for b in build_configs:
|
||||
if b.remove_other_ldflags(flags):
|
||||
self.modified = True
|
||||
|
||||
def add_header_search_paths(self, paths, recursive=True):
|
||||
build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
|
||||
|
||||
@@ -504,6 +594,13 @@ class XcodeProject(PBXDict):
|
||||
if b.add_header_search_paths(paths, recursive):
|
||||
self.modified = True
|
||||
|
||||
def add_framework_search_paths(self, paths, recursive=True):
|
||||
build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
|
||||
|
||||
for b in build_configs:
|
||||
if b.add_framework_search_paths(paths, recursive):
|
||||
self.modified = True
|
||||
|
||||
def add_library_search_paths(self, paths, recursive=True):
|
||||
build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
|
||||
|
||||
@@ -516,9 +613,13 @@ class XcodeProject(PBXDict):
|
||||
def get_obj(self, id):
|
||||
return self.objects.get(id)
|
||||
|
||||
def get_ids(self):
|
||||
return self.objects.keys()
|
||||
|
||||
def get_files_by_os_path(self, os_path, tree='SOURCE_ROOT'):
|
||||
files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference'
|
||||
and f.get('path') == os_path]
|
||||
and f.get('path') == os_path
|
||||
and f.get('sourceTree') == tree]
|
||||
|
||||
return files
|
||||
|
||||
@@ -579,7 +680,7 @@ class XcodeProject(PBXDict):
|
||||
path = os.path.abspath(path)
|
||||
|
||||
groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'
|
||||
and os.path.abspath(g.get('path','/dev/null')) == path]
|
||||
and os.path.abspath(g.get('path', '/dev/null')) == path]
|
||||
|
||||
return groups
|
||||
|
||||
@@ -618,7 +719,7 @@ class XcodeProject(PBXDict):
|
||||
# assume it's an id
|
||||
parent = self.objects.get(parent, self.root_group)
|
||||
|
||||
path_dict = {os.path.split(os_path)[0]:parent}
|
||||
path_dict = {os.path.split(os_path)[0]: parent}
|
||||
special_list = []
|
||||
|
||||
for (grp_path, subdirs, files) in os.walk(os_path):
|
||||
@@ -635,7 +736,6 @@ class XcodeProject(PBXDict):
|
||||
if os.path.splitext(grp_path)[1] in XcodeProject.special_folders:
|
||||
# if this file has a special extension (bundle or framework mainly) treat it as a file
|
||||
special_list.append(grp_path)
|
||||
|
||||
new_files = self.verify_files([folder_name], parent=parent)
|
||||
|
||||
if new_files:
|
||||
@@ -644,7 +744,7 @@ class XcodeProject(PBXDict):
|
||||
continue
|
||||
|
||||
# create group
|
||||
grp = self.get_or_create_group(folder_name, path=self.get_relative_path(grp_path) , parent=parent)
|
||||
grp = self.get_or_create_group(folder_name, path=self.get_relative_path(grp_path), parent=parent)
|
||||
path_dict[grp_path] = grp
|
||||
|
||||
results.append(grp)
|
||||
@@ -652,7 +752,7 @@ class XcodeProject(PBXDict):
|
||||
file_dict = {}
|
||||
|
||||
for f in files:
|
||||
if f[0] == '.' or [m for m in excludes if re.match(m,f)]:
|
||||
if f[0] == '.' or [m for m in excludes if re.match(m, f)]:
|
||||
continue
|
||||
|
||||
kwds = {
|
||||
@@ -667,7 +767,7 @@ class XcodeProject(PBXDict):
|
||||
|
||||
new_files = self.verify_files([n.get('name') for n in file_dict.values()], parent=grp)
|
||||
|
||||
add_files = [(k,v) for k,v in file_dict.items() if v.get('name') in new_files]
|
||||
add_files = [(k, v) for k, v in file_dict.items() if v.get('name') in new_files]
|
||||
|
||||
for path, kwds in add_files:
|
||||
kwds.pop('name', None)
|
||||
@@ -682,9 +782,20 @@ class XcodeProject(PBXDict):
|
||||
|
||||
return results
|
||||
|
||||
def add_file(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False):
|
||||
results = []
|
||||
def path_leaf(self, path):
|
||||
head, tail = ntpath.split(path)
|
||||
return tail or ntpath.basename(head)
|
||||
|
||||
def add_file_if_doesnt_exist(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False, ignore_unknown_type=True):
|
||||
for obj in self.objects.values():
|
||||
if 'path' in obj:
|
||||
if self.path_leaf(f_path) == self.path_leaf(obj.get('path')):
|
||||
return []
|
||||
|
||||
return self.add_file(f_path, parent, tree, create_build_files, weak, ignore_unknown_type=ignore_unknown_type)
|
||||
|
||||
def add_file(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False, ignore_unknown_type=True):
|
||||
results = []
|
||||
abs_path = ''
|
||||
|
||||
if os.path.isabs(f_path):
|
||||
@@ -703,9 +814,10 @@ class XcodeProject(PBXDict):
|
||||
# assume it's an id
|
||||
parent = self.objects.get(parent, self.root_group)
|
||||
|
||||
file_ref = PBXFileReference.Create(f_path, tree)
|
||||
file_ref = PBXFileReference.Create(f_path, tree, ignore_unknown_type=ignore_unknown_type)
|
||||
parent.add_child(file_ref)
|
||||
results.append(file_ref)
|
||||
|
||||
# create a build file for the file ref
|
||||
if file_ref.build_phase and create_build_files:
|
||||
phases = self.get_build_phases(file_ref.build_phase)
|
||||
@@ -716,12 +828,17 @@ class XcodeProject(PBXDict):
|
||||
phase.add_build_file(build_file)
|
||||
results.append(build_file)
|
||||
|
||||
if abs_path and tree == 'SOURCE_ROOT' and os.path.isfile(abs_path)\
|
||||
if abs_path and tree == 'SOURCE_ROOT' \
|
||||
and os.path.isfile(abs_path) \
|
||||
and file_ref.build_phase == 'PBXFrameworksBuildPhase':
|
||||
library_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0])
|
||||
self.add_library_search_paths([library_path], recursive=False)
|
||||
|
||||
library_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0])
|
||||
|
||||
self.add_library_search_paths([library_path], recursive=False)
|
||||
if abs_path and tree == 'SOURCE_ROOT' \
|
||||
and not os.path.isfile(abs_path) \
|
||||
and file_ref.build_phase == 'PBXFrameworksBuildPhase':
|
||||
framework_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0])
|
||||
self.add_framework_search_paths([framework_path, '$(inherited)'], recursive=False)
|
||||
|
||||
for r in results:
|
||||
self.objects[r.id] = r
|
||||
@@ -731,14 +848,44 @@ class XcodeProject(PBXDict):
|
||||
|
||||
return results
|
||||
|
||||
def check_and_repair_framework(self, base):
|
||||
name = os.path.basename(base)
|
||||
|
||||
if ".framework" in name:
|
||||
basename = name[:-len(".framework")]
|
||||
|
||||
finalHeaders = os.path.join(base, "Headers")
|
||||
finalCurrent = os.path.join(base, "Versions/Current")
|
||||
finalLib = os.path.join(base, basename)
|
||||
srcHeaders = "Versions/A/Headers"
|
||||
srcCurrent = "A"
|
||||
srcLib = "Versions/A/" + basename
|
||||
|
||||
if not os.path.exists(finalHeaders):
|
||||
os.symlink(srcHeaders, finalHeaders)
|
||||
if not os.path.exists(finalCurrent):
|
||||
os.symlink(srcCurrent, finalCurrent)
|
||||
if not os.path.exists(finalLib):
|
||||
os.symlink(srcLib, finalLib)
|
||||
|
||||
def remove_group(self, grp):
|
||||
pass
|
||||
|
||||
def remove_file(self, path, parent):
|
||||
files = self.get_files_by_os_path(path)
|
||||
for r in files:
|
||||
del self.objects[r.id]
|
||||
self.modified = True
|
||||
def remove_file(self, id, recursive=True):
|
||||
if not PBXType.IsGuid(id):
|
||||
id = id.id
|
||||
|
||||
if id in self.objects:
|
||||
self.objects.remove(id)
|
||||
|
||||
if recursive:
|
||||
groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup']
|
||||
|
||||
for group in groups:
|
||||
if id in group['children']:
|
||||
group.remove_child(id)
|
||||
|
||||
self.modified = True
|
||||
|
||||
def move_file(self, id, dest_grp=None):
|
||||
pass
|
||||
@@ -750,7 +897,7 @@ class XcodeProject(PBXDict):
|
||||
|
||||
print 'applying "%s" to "%s"' % (patch_path, xcode_path)
|
||||
|
||||
return subprocess.call(['patch', '-p1', '--forward', '--directory=%s'%xcode_path, '--input=%s'%patch_path])
|
||||
return subprocess.call(['patch', '-p1', '--forward', '--directory=%s' % xcode_path, '--input=%s' % patch_path])
|
||||
|
||||
def apply_mods(self, mod_dict, default_path=None):
|
||||
if not default_path:
|
||||
@@ -760,7 +907,6 @@ class XcodeProject(PBXDict):
|
||||
|
||||
for k in keys:
|
||||
v = mod_dict.pop(k)
|
||||
|
||||
mod_dict[k.lower()] = v
|
||||
|
||||
parent = mod_dict.pop('group', None)
|
||||
@@ -775,7 +921,7 @@ class XcodeProject(PBXDict):
|
||||
|
||||
compiler_flags = mod_dict.pop('compiler_flags', {})
|
||||
|
||||
for k,v in mod_dict.items():
|
||||
for k, v in mod_dict.items():
|
||||
if k == 'patches':
|
||||
for p in v:
|
||||
if not os.path.isabs(p):
|
||||
@@ -822,7 +968,6 @@ class XcodeProject(PBXDict):
|
||||
continue
|
||||
|
||||
p = self.get_relative_path(p)
|
||||
|
||||
paths.append(os.path.join('$(SRCROOT)', p, "**"))
|
||||
|
||||
if k == 'headerpaths':
|
||||
@@ -831,6 +976,8 @@ class XcodeProject(PBXDict):
|
||||
self.add_library_search_paths(paths)
|
||||
elif k == 'other_cflags':
|
||||
self.add_other_cflags(v)
|
||||
elif k == 'other_ldflags':
|
||||
self.add_other_ldflags(v)
|
||||
elif k == 'libs' or k == 'frameworks' or k == 'files':
|
||||
paths = {}
|
||||
|
||||
@@ -847,7 +994,7 @@ class XcodeProject(PBXDict):
|
||||
file_path = os.path.join(default_path, p)
|
||||
search_path, file_name = os.path.split(file_path)
|
||||
|
||||
if [m for m in excludes if re.match(m,file_name)]:
|
||||
if [m for m in excludes if re.match(m, file_name)]:
|
||||
continue
|
||||
|
||||
try:
|
||||
@@ -859,10 +1006,10 @@ class XcodeProject(PBXDict):
|
||||
file_list = os.listdir(search_path)
|
||||
|
||||
for f in file_list:
|
||||
if [m for m in excludes if re.match(m,f)]:
|
||||
if [m for m in excludes if re.match(m, f)]:
|
||||
continue
|
||||
|
||||
if re.search(expr,f):
|
||||
if re.search(expr, f):
|
||||
kwds['name'] = f
|
||||
paths[os.path.join(search_path, f)] = kwds
|
||||
p = None
|
||||
@@ -876,10 +1023,10 @@ class XcodeProject(PBXDict):
|
||||
kwds['name'] = file_name
|
||||
|
||||
if k == 'libs':
|
||||
p = os.path.join('usr','lib',p)
|
||||
p = os.path.join('usr', 'lib', p)
|
||||
kwds['tree'] = 'SDKROOT'
|
||||
elif k == 'frameworks':
|
||||
p = os.path.join('System','Library','Frameworks',p)
|
||||
p = os.path.join('System', 'Library', 'Frameworks', p)
|
||||
kwds['tree'] = 'SDKROOT'
|
||||
elif k == 'files' and not os.path.exists(file_path):
|
||||
# don't add non-existent files to the project.
|
||||
@@ -888,26 +1035,24 @@ class XcodeProject(PBXDict):
|
||||
paths[p] = kwds
|
||||
|
||||
new_files = self.verify_files([n.get('name') for n in paths.values()])
|
||||
|
||||
add_files = [(k,v) for k,v in paths.items() if v.get('name') in new_files]
|
||||
add_files = [(k, v) for k, v in paths.items() if v.get('name') in new_files]
|
||||
|
||||
for path, kwds in add_files:
|
||||
kwds.pop('name', None)
|
||||
|
||||
if not kwds.has_key('parent') and parent:
|
||||
if 'parent' not in kwds and parent:
|
||||
kwds['parent'] = parent
|
||||
|
||||
self.add_file(path, **kwds)
|
||||
|
||||
if compiler_flags:
|
||||
for k,v in compiler_flags.items():
|
||||
for k, v in compiler_flags.items():
|
||||
filerefs = []
|
||||
|
||||
for f in v:
|
||||
filerefs.extend([fr.id for fr in self.objects.values() if fr.get('isa') == 'PBXFileReference'
|
||||
and fr.get('name') == f])
|
||||
|
||||
|
||||
buildfiles = [bf for bf in self.objects.values() if bf.get('isa') == 'PBXBuildFile'
|
||||
and bf.get('fileRef') in filerefs]
|
||||
|
||||
@@ -915,54 +1060,268 @@ class XcodeProject(PBXDict):
|
||||
if bf.add_compiler_flag(k):
|
||||
self.modified = True
|
||||
|
||||
|
||||
def backup(self, file_name=None):
|
||||
def backup(self, file_name=None, backup_name=None):
|
||||
if not file_name:
|
||||
file_name = self.pbxproj_path
|
||||
|
||||
backup_name = "%s.%s.backup" % (file_name, datetime.datetime.now().strftime('%d%m%y-%H%M%S'))
|
||||
if not backup_name:
|
||||
backup_name = "%s.%s.backup" % (file_name, datetime.datetime.now().strftime('%d%m%y-%H%M%S'))
|
||||
|
||||
shutil.copy2(file_name, backup_name)
|
||||
|
||||
def save(self, file_name=None):
|
||||
"""Saves in old (xml) format"""
|
||||
if not file_name:
|
||||
file_name = self.pbxproj_path
|
||||
|
||||
# JSON serialize the project and convert that json to an xml plist
|
||||
p = subprocess.Popen([XcodeProject.plutil_path, '-convert', 'xml1', '-o', file_name, '-'], stdin=subprocess.PIPE)
|
||||
p.communicate(PBXEncoder().encode(self.data))
|
||||
# This code is adapted from plistlib.writePlist
|
||||
with open(file_name, "w") as f:
|
||||
writer = PBXWriter(f)
|
||||
writer.writeln("<plist version=\"1.0\">")
|
||||
writer.writeValue(self.data)
|
||||
writer.writeln("</plist>")
|
||||
|
||||
def saveFormat3_2(self, file_name=None):
|
||||
"""Save in Xcode 3.2 compatible (new) format"""
|
||||
if not file_name:
|
||||
file_name = self.pbxproj_path
|
||||
|
||||
# process to get the section's info and names
|
||||
objs = self.data.get('objects')
|
||||
sections = dict()
|
||||
uuids = dict()
|
||||
|
||||
for key in objs:
|
||||
l = list()
|
||||
|
||||
if objs.get(key).get('isa') in sections:
|
||||
l = sections.get(objs.get(key).get('isa'))
|
||||
|
||||
l.append(tuple([key, objs.get(key)]))
|
||||
sections[objs.get(key).get('isa')] = l
|
||||
|
||||
if 'name' in objs.get(key):
|
||||
uuids[key] = objs.get(key).get('name')
|
||||
elif 'path' in objs.get(key):
|
||||
uuids[key] = objs.get(key).get('path')
|
||||
else:
|
||||
if objs.get(key).get('isa') == 'PBXProject':
|
||||
uuids[objs.get(key).get('buildConfigurationList')] = 'Build configuration list for PBXProject "Unity-iPhone"'
|
||||
elif objs.get(key).get('isa')[0:3] == 'PBX':
|
||||
uuids[key] = objs.get(key).get('isa')[3:-10]
|
||||
else:
|
||||
uuids[key] = 'Build configuration list for PBXNativeTarget "TARGET_NAME"'
|
||||
|
||||
ro = self.data.get('rootObject')
|
||||
uuids[ro] = 'Project Object'
|
||||
|
||||
for key in objs:
|
||||
# transitive references (used in the BuildFile section)
|
||||
if 'fileRef' in objs.get(key) and objs.get(key).get('fileRef') in uuids:
|
||||
uuids[key] = uuids[objs.get(key).get('fileRef')]
|
||||
|
||||
# transitive reference to the target name (used in the Native target section)
|
||||
if objs.get(key).get('isa') == 'PBXNativeTarget':
|
||||
uuids[objs.get(key).get('buildConfigurationList')] = uuids[objs.get(key).get('buildConfigurationList')].replace('TARGET_NAME', uuids[key])
|
||||
|
||||
self.uuids = uuids
|
||||
self.sections = sections
|
||||
|
||||
out = open(file_name, 'w')
|
||||
out.write('// !$*UTF8*$!\n')
|
||||
self._printNewXCodeFormat(out, self.data, '', enters=True)
|
||||
out.close()
|
||||
|
||||
@classmethod
|
||||
def addslashes(cls, s):
|
||||
d = {'"': '\\"', "'": "\\'", "\0": "\\\0", "\\": "\\\\"}
|
||||
return ''.join(d.get(c, c) for c in s)
|
||||
|
||||
def _printNewXCodeFormat(self, out, root, deep, enters=True):
|
||||
if isinstance(root, IterableUserDict):
|
||||
out.write('{')
|
||||
|
||||
if enters:
|
||||
out.write('\n')
|
||||
|
||||
isa = root.pop('isa', '')
|
||||
|
||||
if isa != '': # keep the isa in the first spot
|
||||
if enters:
|
||||
out.write('\t' + deep)
|
||||
|
||||
out.write('isa = ')
|
||||
self._printNewXCodeFormat(out, isa, '\t' + deep, enters=enters)
|
||||
out.write(';')
|
||||
|
||||
if enters:
|
||||
out.write('\n')
|
||||
else:
|
||||
out.write(' ')
|
||||
|
||||
for key in sorted(root.iterkeys()): # keep the same order as Apple.
|
||||
if enters:
|
||||
out.write('\t' + deep)
|
||||
|
||||
if re.match(regex, key).group(0) == key:
|
||||
out.write(key.encode("utf-8") + ' = ')
|
||||
else:
|
||||
out.write('"' + key.encode("utf-8") + '" = ')
|
||||
|
||||
if key == 'objects':
|
||||
out.write('{') # open the objects section
|
||||
|
||||
if enters:
|
||||
out.write('\n')
|
||||
#root.remove('objects') # remove it to avoid problems
|
||||
|
||||
sections = [
|
||||
('PBXBuildFile', False),
|
||||
('PBXCopyFilesBuildPhase', True),
|
||||
('PBXFileReference', False),
|
||||
('PBXFrameworksBuildPhase', True),
|
||||
('PBXGroup', True),
|
||||
('PBXNativeTarget', True),
|
||||
('PBXProject', True),
|
||||
('PBXResourcesBuildPhase', True),
|
||||
('PBXShellScriptBuildPhase', True),
|
||||
('PBXSourcesBuildPhase', True),
|
||||
('XCBuildConfiguration', True),
|
||||
('XCConfigurationList', True),
|
||||
('PBXTargetDependency', True),
|
||||
('PBXVariantGroup', True),
|
||||
('PBXReferenceProxy', True),
|
||||
('PBXContainerItemProxy', True)]
|
||||
|
||||
for section in sections: # iterate over the sections
|
||||
if self.sections.get(section[0]) is None:
|
||||
continue
|
||||
|
||||
out.write('\n/* Begin %s section */' % section[0].encode("utf-8"))
|
||||
self.sections.get(section[0]).sort(cmp=lambda x, y: cmp(x[0], y[0]))
|
||||
|
||||
for pair in self.sections.get(section[0]):
|
||||
key = pair[0]
|
||||
value = pair[1]
|
||||
out.write('\n')
|
||||
|
||||
if enters:
|
||||
out.write('\t\t' + deep)
|
||||
|
||||
out.write(key.encode("utf-8"))
|
||||
|
||||
if key in self.uuids:
|
||||
out.write(" /* " + self.uuids[key].encode("utf-8") + " */")
|
||||
|
||||
out.write(" = ")
|
||||
self._printNewXCodeFormat(out, value, '\t\t' + deep, enters=section[1])
|
||||
out.write(';')
|
||||
|
||||
out.write('\n/* End %s section */\n' % section[0].encode("utf-8"))
|
||||
|
||||
out.write(deep + '\t}') # close of the objects section
|
||||
else:
|
||||
self._printNewXCodeFormat(out, root[key], '\t' + deep, enters=enters)
|
||||
|
||||
out.write(';')
|
||||
|
||||
if enters:
|
||||
out.write('\n')
|
||||
else:
|
||||
out.write(' ')
|
||||
|
||||
root['isa'] = isa # restore the isa for further calls
|
||||
|
||||
if enters:
|
||||
out.write(deep)
|
||||
|
||||
out.write('}')
|
||||
|
||||
elif isinstance(root, UserList):
|
||||
out.write('(')
|
||||
|
||||
if enters:
|
||||
out.write('\n')
|
||||
|
||||
for value in root:
|
||||
if enters:
|
||||
out.write('\t' + deep)
|
||||
|
||||
self._printNewXCodeFormat(out, value, '\t' + deep, enters=enters)
|
||||
out.write(',')
|
||||
|
||||
if enters:
|
||||
out.write('\n')
|
||||
|
||||
if enters:
|
||||
out.write(deep)
|
||||
|
||||
out.write(')')
|
||||
|
||||
else:
|
||||
if len(root) > 0 and re.match(regex, root).group(0) == root:
|
||||
out.write(root.encode("utf-8"))
|
||||
else:
|
||||
out.write('"' + XcodeProject.addslashes(root.encode("utf-8")) + '"')
|
||||
|
||||
if root in self.uuids:
|
||||
out.write(" /* " + self.uuids[root].encode("utf-8") + " */")
|
||||
|
||||
@classmethod
|
||||
def Load(cls, path):
|
||||
cls.plutil_path = os.path.join(os.path.split(__file__)[0], 'plutil')
|
||||
|
||||
|
||||
if not os.path.isfile(XcodeProject.plutil_path):
|
||||
cls.plutil_path = 'plutil'
|
||||
|
||||
if subprocess.call([XcodeProject.plutil_path,'-lint','-s',path]):
|
||||
print 'ERROR: not a valid .pbxproj file'
|
||||
|
||||
# load project by converting to xml and then convert that using plistlib
|
||||
p = subprocess.Popen([XcodeProject.plutil_path, '-convert', 'xml1', '-o', '-', path], stdout=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
|
||||
# If the plist was malformed, returncode will be non-zero
|
||||
if p.returncode != 0:
|
||||
print stdout
|
||||
return None
|
||||
|
||||
# load project by converting to JSON and parse
|
||||
p = subprocess.Popen([XcodeProject.plutil_path, '-convert', 'json', '-o', '-', path], stdout=subprocess.PIPE)
|
||||
tree = json.loads(p.communicate()[0])
|
||||
|
||||
|
||||
tree = plistlib.readPlistFromString(stdout)
|
||||
return XcodeProject(tree, path)
|
||||
|
||||
|
||||
def test(argv=None):
|
||||
if not argv:
|
||||
argv = sys.argv
|
||||
# The code below was adapted from plistlib.py.
|
||||
|
||||
proj = XcodeProject.Load('../../Build/Unity-iPhone.xcodeproj/project.pbxproj')
|
||||
class PBXWriter(plistlib.PlistWriter):
|
||||
def writeValue(self, value):
|
||||
if isinstance(value, (PBXList, PBXDict)):
|
||||
plistlib.PlistWriter.writeValue(self, value.data)
|
||||
else:
|
||||
plistlib.PlistWriter.writeValue(self, value)
|
||||
|
||||
def simpleElement(self, element, value=None):
|
||||
"""
|
||||
We have to override this method to deal with Unicode text correctly.
|
||||
Non-ascii characters have to get encoded as character references.
|
||||
"""
|
||||
if value is not None:
|
||||
value = _escapeAndEncode(value)
|
||||
self.writeln("<%s>%s</%s>" % (element, value, element))
|
||||
else:
|
||||
self.writeln("<%s/>" % element)
|
||||
|
||||
proj.add_folder('../Assets/Editor/Airship/UI/Default/StoreFront')
|
||||
|
||||
proj.add_file('../Assets/Editor/Airship/libUAirship-1.1.4.a')
|
||||
proj.add_file('../Assets/Plugins/Airship/AirshipConfig.plist')
|
||||
# Regex to find any control chars, except for \t \n and \r
|
||||
_controlCharPat = re.compile(
|
||||
r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"
|
||||
r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
|
||||
|
||||
proj.backup()
|
||||
proj.save()
|
||||
|
||||
print str(proj)
|
||||
|
||||
def _escapeAndEncode(text):
|
||||
m = _controlCharPat.search(text)
|
||||
if m is not None:
|
||||
raise ValueError("strings can't contains control characters; "
|
||||
"use plistlib.Data instead")
|
||||
text = text.replace("\r\n", "\n") # convert DOS line endings
|
||||
text = text.replace("\r", "\n") # convert Mac line endings
|
||||
text = text.replace("&", "&") # escape '&'
|
||||
text = text.replace("<", "<") # escape '<'
|
||||
text = text.replace(">", ">") # escape '>'
|
||||
return text.encode("ascii", "xmlcharrefreplace") # encode as ascii with xml character references
|
||||
@@ -2,7 +2,7 @@
|
||||
* parser.j
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Modified by Antoine Mercadal, with great help of Martin Carlberg
|
||||
* Modified by Antoine Mercadal, with great help from Martin Carlberg
|
||||
* Copyright 2008-2013, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
@@ -22,29 +22,37 @@
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
|
||||
var FILE = require("file");
|
||||
var FILE = require("file"),
|
||||
OS = require("os"),
|
||||
stream = require("narwhal/term").stream,
|
||||
|
||||
SLASH_REPLACEMENT = "∕"; // DIVISION SLASH, Unicode: U+2215
|
||||
|
||||
// Debug function to print some JS objects
|
||||
function dump(obj)
|
||||
{
|
||||
CPLogPrint(JSON.stringify(obj));
|
||||
print(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
var xcc = ObjectiveJ.acorn.walk.make(
|
||||
function raise(pos, message)
|
||||
{
|
||||
var syntaxError = new SyntaxError(message);
|
||||
syntaxError.line = pos.line;
|
||||
|
||||
throw syntaxError;
|
||||
}
|
||||
|
||||
var errors = [],
|
||||
xcc = ObjectiveJ.acorn.walk.make(
|
||||
{
|
||||
ClassDeclarationStatement: function(node, st, c)
|
||||
{
|
||||
if (node.categoryname)
|
||||
{
|
||||
CPLogPrint("Categories are not supported yet. Ignoring it.");
|
||||
return;
|
||||
}
|
||||
|
||||
var className = node.classname.name,
|
||||
superclassname = node.superclassname.name,
|
||||
superclassname = node.superclassname ? node.superclassname.name : "",
|
||||
declaredOutletsName = [],
|
||||
classInfo = {
|
||||
"name": className,
|
||||
"category": node.categoryname ? node.categoryname.name : "",
|
||||
"superClass": superclassname,
|
||||
"outlets": [],
|
||||
"actions": [],
|
||||
@@ -63,7 +71,7 @@ var xcc = ObjectiveJ.acorn.walk.make(
|
||||
if (ivarHasOutlet)
|
||||
{
|
||||
if (declaredOutletsName.indexOf(ivarName) !== -1)
|
||||
throw("Outlet named '" + ivarName + "' is declared multiple times.");
|
||||
raise(ivarDecl.loc.start, "Outlet '" + ivarName + "' declared more than once");
|
||||
|
||||
declaredOutletsName.push(ivarName);
|
||||
classInfo.outlets.push({"type": ivarType, "name": ivarName});
|
||||
@@ -84,29 +92,31 @@ var xcc = ObjectiveJ.acorn.walk.make(
|
||||
methodReturnType = [node.returntype ? node.returntype.name : "id"],
|
||||
methodHasAction = node.action ? "IBAction" : null,
|
||||
selector = selectors[0].name,
|
||||
actionInformations = {"name": selector, "arguments":[]};
|
||||
actionInfo = {"name": selector, "arguments":[]};
|
||||
|
||||
if (methodHasAction && arguments.length == 1)
|
||||
if (methodHasAction)
|
||||
{
|
||||
if (st.actionNames.indexOf(selector) !== -1)
|
||||
throw("Action named '" + selector + "' is declared multiple times.");
|
||||
|
||||
st.actionNames.push(selector);
|
||||
|
||||
for (var i = 0; i < arguments.length; i++)
|
||||
if (arguments.length == 1)
|
||||
{
|
||||
var argument = arguments[i],
|
||||
argumentName = argument.identifier.name,
|
||||
argumentType = argument.type ? argument.type.name : null;
|
||||
if (st.actionNames.indexOf(selector) !== -1)
|
||||
raise(node.loc.start, "Action '" + selector + "' declared more than once");
|
||||
|
||||
actionInformations.arguments.push({"type": argumentType, "name": argumentName});
|
||||
st.actionNames.push(selector);
|
||||
|
||||
for (var i = 0; i < arguments.length; i++)
|
||||
{
|
||||
var argument = arguments[i],
|
||||
argumentName = argument.identifier.name,
|
||||
argumentType = argument.type ? argument.type.name : null;
|
||||
|
||||
actionInfo.arguments.push({"type": argumentType, "name": argumentName});
|
||||
}
|
||||
|
||||
st.actions.push(actionInfo)
|
||||
}
|
||||
|
||||
st.actions.push(actionInformations)
|
||||
else
|
||||
raise(node.loc.start, "Action methods must have exactly one parameter");
|
||||
}
|
||||
else if (methodHasAction)
|
||||
throw("Method '" + selector + "' is an action but has more than one parameter.");
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -121,63 +131,109 @@ function compile(node, state, visitor)
|
||||
c(node, state);
|
||||
};
|
||||
|
||||
function shadowBaseNameForPath(projectBasePath, path)
|
||||
{
|
||||
// Make the path project-relative
|
||||
path = path.substring(projectBasePath.length + 1, path.length);
|
||||
|
||||
// strip the extension and replace slashes
|
||||
return path.substring(0, path.length - 2).replace(/[/]/g, SLASH_REPLACEMENT);
|
||||
}
|
||||
|
||||
/*
|
||||
$1 Project base path
|
||||
$2 Full project source path
|
||||
*/
|
||||
function main(args)
|
||||
{
|
||||
var fileURL = new CFURL(args[1]),
|
||||
outputHeaderURL = new CFURL(args[2]),
|
||||
outputSourceURL = new CFURL(args[3]),
|
||||
source = FILE.read(fileURL, { charset: "UTF-8" }),
|
||||
flags = ObjectiveJ.Preprocessor.Flags.IncludeDebugSymbols | ObjectiveJ.Preprocessor.Flags.IncludeTypeSignatures,
|
||||
tokens = ObjectiveJ.acorn.parse(source),
|
||||
classesInformation = [],
|
||||
ObjectiveCSource = "",
|
||||
ObjectiveCHeader = "";
|
||||
|
||||
|
||||
compile(tokens, classesInformation, xcc);
|
||||
|
||||
// dump(classesInformation)
|
||||
|
||||
ObjectiveCHeader +=
|
||||
"#import <Foundation/Foundation.h>\n" +
|
||||
"#import <Cocoa/Cocoa.h>\n" +
|
||||
"#import \"xcc_general_include.h\"\n\n";
|
||||
|
||||
ObjectiveCSource += "#import \"" + outputHeaderURL.absoluteString().replace(/\\/g,'/').replace(/(.*\/)/g, '') + "\"\n\n";
|
||||
|
||||
// Traverse each found classes
|
||||
classesInformation.forEach(function(aClass)
|
||||
try
|
||||
{
|
||||
// add new class definition
|
||||
ObjectiveCHeader += "@interface " + aClass.name + " : " + NSCompatibleClassName(aClass.superClass) + "\n\n";
|
||||
var projectBasePath = args[1],
|
||||
sourcePath = args[2],
|
||||
outputDirectory = [projectBasePath stringByAppendingPathComponent:@".XcodeSupport"],
|
||||
baseFilename = shadowBaseNameForPath(projectBasePath, sourcePath),
|
||||
outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilename + ".h"]),
|
||||
outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilename + ".m"]),
|
||||
source = FILE.read(sourcePath, { charset: "UTF-8" }),
|
||||
flags = ObjectiveJ.Preprocessor.Flags.IncludeDebugSymbols | ObjectiveJ.Preprocessor.Flags.IncludeTypeSignatures,
|
||||
tokens = ObjectiveJ.acorn.parse(source, { locations:true, sourceFile:sourcePath }),
|
||||
classesInformation = [],
|
||||
ObjectiveCSource = "",
|
||||
ObjectiveCHeader = "",
|
||||
hasErrors = NO;
|
||||
|
||||
// Add each outlets in header
|
||||
aClass.outlets.forEach(function(anOutlet)
|
||||
compile(tokens, classesInformation, xcc);
|
||||
|
||||
// dump(classesInformation)
|
||||
|
||||
ObjectiveCHeader +=
|
||||
"#import <Cocoa/Cocoa.h>\n" +
|
||||
'#import "xcc_general_include.h"\n';
|
||||
|
||||
ObjectiveCSource += "#import \"" + outputHeaderURL.lastPathComponent() + "\"\n";
|
||||
|
||||
// Traverse each found classes
|
||||
classesInformation.forEach(function(aClass)
|
||||
{
|
||||
ObjectiveCHeader += "@property (assign) IBOutlet " + NSCompatibleClassName(anOutlet.type, YES) + " " + anOutlet.name + ";\n";
|
||||
// add new class definition
|
||||
if (aClass.superClass)
|
||||
ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ : %@", aClass.name, NSCompatibleClassName(aClass.superClass)];
|
||||
else
|
||||
ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ (%@)", aClass.name, aClass.category];
|
||||
|
||||
// add each outlet in header
|
||||
if (aClass.outlets.length > 0)
|
||||
ObjectiveCHeader += "\n";
|
||||
|
||||
aClass.outlets.forEach(function(anOutlet)
|
||||
{
|
||||
ObjectiveCHeader += [CPString stringWithFormat:@"\n@property (assign) IBOutlet %@ %@;", NSCompatibleClassName(anOutlet.type, YES), anOutlet.name];
|
||||
});
|
||||
|
||||
if (aClass.actions.length > 0)
|
||||
ObjectiveCHeader += "\n";
|
||||
|
||||
// add each action in header
|
||||
aClass.actions.forEach(function(anAction)
|
||||
{
|
||||
ObjectiveCHeader += [CPString stringWithFormat:@"\n- (IBAction)%@:(%@)%@;", anAction.name, anAction.arguments[0].type, anAction.arguments[0].name];
|
||||
});
|
||||
|
||||
if (aClass.outlets.length > 0 || aClass.actions.length > 0)
|
||||
ObjectiveCHeader += "\n";
|
||||
|
||||
ObjectiveCHeader += "\n@end\n";
|
||||
|
||||
// fill up the implementation file
|
||||
ObjectiveCSource += "\n@implementation " + aClass.name + "\n@end\n";
|
||||
});
|
||||
|
||||
ObjectiveCHeader += "\n";
|
||||
if (ObjectiveCSource.length)
|
||||
FILE.write(outputImplementationURL, ObjectiveCSource, { charset:"UTF-8" });
|
||||
|
||||
// Add each actions in header
|
||||
aClass.actions.forEach(function(anAction)
|
||||
{
|
||||
ObjectiveCHeader += "- (IBAction)" + anAction.name + ":(" + anAction.arguments[0].type + ")" + anAction.arguments[0].name + ";\n";
|
||||
});
|
||||
if (ObjectiveCHeader.length)
|
||||
FILE.write(outputHeaderURL, ObjectiveCHeader, { charset:"UTF-8" });
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
[errors addObject:@{
|
||||
@"message": e.message,
|
||||
@"path": sourcePath,
|
||||
@"line": e.line
|
||||
}];
|
||||
|
||||
ObjectiveCHeader += "\n@end\n\n\n";
|
||||
hasErrors = YES;
|
||||
}
|
||||
|
||||
// fill up the implementation file
|
||||
ObjectiveCSource += "@implementation " + aClass.name + "\n@end\n\n";
|
||||
});
|
||||
if ([errors count])
|
||||
{
|
||||
var plist = [CPPropertyListSerialization dataFromPropertyList:errors format:CPPropertyListXMLFormat_v1_0];
|
||||
|
||||
// write files
|
||||
if (ObjectiveCSource.length)
|
||||
FILE.write(outputSourceURL, ObjectiveCSource, { charset:"UTF-8" });
|
||||
stream.printError([plist rawString]);
|
||||
|
||||
if (ObjectiveCHeader.length)
|
||||
FILE.write(outputHeaderURL, ObjectiveCHeader, { charset:"UTF-8" });
|
||||
// If there were category warnings, hasErrors is NO, so return a warning status
|
||||
OS.exit(hasErrors ? 1 : 2);
|
||||
}
|
||||
}
|
||||
|
||||
function NSCompatibleClassName(aClassName, asPointer)
|
||||
@@ -514,6 +570,7 @@ var NSClasses = {
|
||||
"NSStepper" : YES,
|
||||
"NSStepperCell" : YES,
|
||||
"NSString Application Kit Additions" : YES,
|
||||
"NSTableCellView" : YES,
|
||||
"NSTableColumn" : YES,
|
||||
"NSTableHeaderCell" : YES,
|
||||
"NSTableHeaderView" : YES,
|
||||
@@ -0,0 +1,240 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os.path
|
||||
import re
|
||||
import sys
|
||||
from mod_pbxproj import XcodeProject
|
||||
|
||||
|
||||
class PBXModifier (object):
|
||||
|
||||
XCODE_SUPPORT_FOLDER = u".XcodeSupport"
|
||||
SLASH_REPLACEMENT = u"∕" # DIVISION SLASH Unicode U+2215
|
||||
FRAMEWORKS_RE = re.compile(ur"^(.+/Frameworks/(?:Debug|Source)/([^/]+))/.+$")
|
||||
XCC_GENERAL_INCLUDE = u"xcc_general_include.h"
|
||||
|
||||
def __init__(self, projectRootPath=None):
|
||||
self.projectRootPath = projectRootPath
|
||||
projectName = os.path.basename(projectRootPath)
|
||||
self.pbxPath = os.path.join(projectRootPath, projectName + u".xcodeproj", u"project.pbxproj")
|
||||
self.project = XcodeProject.Load(self.pbxPath)
|
||||
|
||||
self.shadowGroup = self.project.get_or_create_group(u"Cocoa Classes")
|
||||
self.sourceGroup = self.project.get_or_create_group(u"Cappuccino Source")
|
||||
self._frameworksGroup = None
|
||||
|
||||
@property
|
||||
def frameworksGroup(self):
|
||||
if self._frameworksGroup is None:
|
||||
self._frameworksGroup = self.project.get_or_create_group(u"Frameworks", parent=self.sourceGroup)
|
||||
|
||||
return self._frameworksGroup
|
||||
|
||||
def update_general_include(self):
|
||||
xcc_general_include_path = os.path.join(self.projectRootPath, self.XCODE_SUPPORT_FOLDER, self.XCC_GENERAL_INCLUDE)
|
||||
content = u""
|
||||
|
||||
for path in os.listdir(os.path.join(self.projectRootPath, self.XCODE_SUPPORT_FOLDER)):
|
||||
filename = unicode(os.path.basename(path))
|
||||
|
||||
if filename.endswith(".h") and filename != self.XCC_GENERAL_INCLUDE:
|
||||
content += u'#include "{0}"\n'.format(filename)
|
||||
|
||||
f = open(xcc_general_include_path, "w")
|
||||
f.write(content.encode("utf-8"))
|
||||
f.close()
|
||||
|
||||
if len(self.project.get_files_by_os_path(os.path.join(self.XCODE_SUPPORT_FOLDER, self.XCC_GENERAL_INCLUDE))) == 0:
|
||||
self.project.add_file(xcc_general_include_path, parent=self.shadowGroup)
|
||||
|
||||
def file_with_path(self, projectSourcePath):
|
||||
relativePath = os.path.relpath(projectSourcePath, self.projectRootPath)
|
||||
|
||||
for fileRef in [f for f in self.project.objects.values() if f.get("isa") == "PBXFileReference"]:
|
||||
filePath = projectSourcePath if fileRef.get("sourceTree") == "<absolute>" else relativePath
|
||||
|
||||
if fileRef.get("path") == filePath:
|
||||
return fileRef
|
||||
|
||||
return None
|
||||
|
||||
def add_file(self, projectSourcePath, shadowHeaderPath, shadowImplementationPath):
|
||||
resolvedPath = os.path.realpath(projectSourcePath)
|
||||
|
||||
# Shadow files are always project-relative
|
||||
if not self.file_with_path(shadowHeaderPath):
|
||||
self.project.add_file(shadowHeaderPath, parent=self.shadowGroup, tree="SOURCE_ROOT", create_build_files=False)
|
||||
|
||||
if not self.file_with_path(shadowImplementationPath):
|
||||
self.project.add_file(shadowImplementationPath, parent=self.shadowGroup, tree="SOURCE_ROOT", create_build_files=False)
|
||||
|
||||
# If the file is within the project directory, the file reference will be project-relative, otherwise absolute
|
||||
if resolvedPath.startswith(self.projectRootPath):
|
||||
tree = "SOURCE_ROOT"
|
||||
else:
|
||||
tree = "<absolute>"
|
||||
|
||||
if not self.file_with_path(resolvedPath):
|
||||
relativePath = os.path.relpath(projectSourcePath, self.projectRootPath)
|
||||
|
||||
if relativePath.startswith(u"Frameworks/"):
|
||||
parent = self.frameworksGroup
|
||||
else:
|
||||
parent = self.sourceGroup
|
||||
|
||||
self.project.add_file(resolvedPath, parent=parent, tree=tree, create_build_files=False)
|
||||
|
||||
def remove_file(self, projectSourcePath, shadowHeaderPath, shadowImplementationPath):
|
||||
resolvedPath = os.path.realpath(projectSourcePath)
|
||||
|
||||
for path in (shadowHeaderPath, shadowImplementationPath, resolvedPath):
|
||||
fileRef = self.file_with_path(path)
|
||||
|
||||
if fileRef:
|
||||
self.project.remove_file(fileRef)
|
||||
|
||||
def add_framework_resources(self, framework, resourcesPath):
|
||||
files = self.project.get_files_by_os_path(resourcesPath, tree="<absolute>")
|
||||
|
||||
if not files:
|
||||
files = self.project.add_file(resourcesPath, parent=None, tree="<absolute>", create_build_files=False)
|
||||
|
||||
if files:
|
||||
files[0]['name'] = framework + u" Resources"
|
||||
|
||||
def compare_file_ids(self, id1, id2):
|
||||
# A few special cases:
|
||||
# - Frameworks group always goes last
|
||||
# - XCC_GENERAL_INCLUDE always goes after another file
|
||||
# - Frameworks/* file always goes after a non-Frameworks file
|
||||
obj1 = self.project.get_obj(id1)
|
||||
name1 = obj1.get("name", obj1.get("path"))
|
||||
|
||||
obj2 = self.project.get_obj(id2)
|
||||
name2 = obj2.get("name", obj2.get("path"))
|
||||
|
||||
# Note: the "∕" in "Frameworks∕" is actually Unicode DIVISION_SLASH, not SOLIDUS (forward slash)
|
||||
if name1 == u"Frameworks" and obj1.get("isa") == u"PBXGroup":
|
||||
return 1
|
||||
elif name2 == u"Frameworks" and obj2.get("isa") == u"PBXGroup":
|
||||
return -1
|
||||
elif name1 == self.XCC_GENERAL_INCLUDE and obj2.get("isa") == u"PBXFileReference":
|
||||
return 1
|
||||
elif name2 == self.XCC_GENERAL_INCLUDE and obj1.get("isa") == u"PBXFileReference":
|
||||
return -1
|
||||
elif name1.startswith(u"Frameworks∕") and not name2.startswith(u"Frameworks∕"):
|
||||
return 1
|
||||
elif name2.startswith(u"Frameworks∕") and not name1.startswith(u"Frameworks∕"):
|
||||
return -1
|
||||
|
||||
return cmp(name1.lower(), name2.lower())
|
||||
|
||||
def compare_resource_folder_ids(self, id1, id2):
|
||||
folder1 = self.project.get_obj(id1)
|
||||
name1 = folder1.get("name", folder1.get("path"))
|
||||
|
||||
folder2 = self.project.get_obj(id2)
|
||||
name2 = folder2.get("name", folder2.get("path"))
|
||||
|
||||
if name1 == u"Resources":
|
||||
return -1
|
||||
elif name2 == u"Resources":
|
||||
return 1
|
||||
|
||||
return cmp(name1.lower(), name2.lower())
|
||||
|
||||
def sort_project(self):
|
||||
# Sort the files alphabetically in our groups.
|
||||
for group in (self.sourceGroup, self.shadowGroup, self._frameworksGroup):
|
||||
if group is None:
|
||||
continue
|
||||
|
||||
group.get("children").data.sort(cmp=self.compare_file_ids)
|
||||
|
||||
# Move resource folders to the top, Resources at the very top
|
||||
root_ids = self.project.root_group.get("children").data
|
||||
folder_ids = []
|
||||
|
||||
for id in root_ids:
|
||||
item = self.project.get_obj(id)
|
||||
name = item.get("name", item.get("path"))
|
||||
|
||||
if name.endswith(u"Resources") and item.get("lastKnownFileType") == "folder":
|
||||
folder_ids.append(id)
|
||||
|
||||
folder_ids.sort(cmp=self.compare_resource_folder_ids, reverse=True)
|
||||
|
||||
for id in folder_ids:
|
||||
index = root_ids.index(id)
|
||||
del root_ids[index]
|
||||
root_ids.insert(0, id)
|
||||
|
||||
def save_project(self):
|
||||
self.project.backup(backup_name=self.project.pbxproj_path + ".backup")
|
||||
self.sort_project()
|
||||
self.project.saveFormat3_2()
|
||||
|
||||
def update_project_with_source(self, action, projectSourcePath):
|
||||
relativePath = os.path.relpath(projectSourcePath, self.projectRootPath)
|
||||
|
||||
shadowBasePath = os.path.join(projectRootPath, self.XCODE_SUPPORT_FOLDER)
|
||||
shadowBaseName = os.path.splitext(relativePath)[0].replace(u"/", self.SLASH_REPLACEMENT)
|
||||
shadowHeaderPath = os.path.join(shadowBasePath, shadowBaseName + ".h")
|
||||
shadowImplementationPath = os.path.join(shadowBasePath, shadowBaseName + ".m")
|
||||
|
||||
if action == "add":
|
||||
fileRef = self.file_with_path(shadowHeaderPath)
|
||||
|
||||
if not fileRef:
|
||||
self.update_general_include()
|
||||
self.add_file(projectSourcePath, shadowHeaderPath, shadowImplementationPath)
|
||||
|
||||
match = self.FRAMEWORKS_RE.match(projectSourcePath)
|
||||
|
||||
if match:
|
||||
framework = match.group(2)
|
||||
resourcesPath = os.path.realpath(os.path.join(match.group(1), u"Resources"))
|
||||
|
||||
if os.path.isdir(resourcesPath):
|
||||
self.add_framework_resources(framework, resourcesPath)
|
||||
|
||||
elif action == "remove":
|
||||
self.update_general_include()
|
||||
self.remove_file(projectSourcePath, shadowHeaderPath, shadowImplementationPath)
|
||||
|
||||
|
||||
#
|
||||
# Possible ways to call this script:
|
||||
#
|
||||
# "add" projectRootPath file
|
||||
# "remove" projectRootPath file
|
||||
# "update" projectRootPath action file... [action file...]
|
||||
#
|
||||
# File paths are full project paths, no resolved symlinks.
|
||||
# action is "add" or "remove".
|
||||
#
|
||||
if __name__ == "__main__":
|
||||
|
||||
action = sys.argv[1]
|
||||
projectRootPath = unicode(sys.argv[2])
|
||||
modifier = PBXModifier(projectRootPath)
|
||||
|
||||
if action in ("add", "remove"):
|
||||
projectSourcePath = unicode(sys.argv[3])
|
||||
modifier.update_project_with_source(action, projectSourcePath)
|
||||
modifier.save_project()
|
||||
|
||||
elif action == "update":
|
||||
# When the action is "update", it is followed by "add" or "remove", followed by 1+ paths
|
||||
args = sys.argv[3:]
|
||||
action = unicode(args.pop(0))
|
||||
|
||||
while len(args):
|
||||
arg = unicode(args.pop(0))
|
||||
|
||||
if arg in ("add", "remove"):
|
||||
action = arg
|
||||
continue
|
||||
else:
|
||||
modifier.update_project_with_source(action, arg)
|
||||
|
||||
modifier.save_project()
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// UserDefaults.h
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/9/13.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef XcodeCapp_UserDefaults_h
|
||||
#define XcodeCapp_UserDefaults_h
|
||||
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
extern NSString * const kDefaultLastEventId;
|
||||
extern NSString * const kDefaultFirstLaunch;
|
||||
extern NSString * const kDefaultFirstLaunchVersion;
|
||||
extern NSString * const kDefaultXCCAPIMode;
|
||||
extern NSString * const kDefaultXCCReactToInodeMod;
|
||||
extern NSString * const kDefaultXCCReopenLastProject;
|
||||
extern NSString * const kDefaultXCCAutoOpenErrorsPanelOnWarnings;
|
||||
extern NSString * const kDefaultXCCAutoOpenErrorsPanelOnErrors;
|
||||
extern NSString * const kDefaultXCCProjectHistory;
|
||||
extern NSString * const kDefaultLastOpenedPath;
|
||||
extern NSString * const kDefaultPathModificationDates;
|
||||
extern NSString * const kDefaultMaxRecentProjects;
|
||||
extern NSString * const kDefaultLogLevel;
|
||||
extern NSString * const kDefaultAutoOpenXcodeProject;
|
||||
extern NSString * const kDefaultShowProcessingNotices;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// UserDefaults.m
|
||||
// XcodeCapp
|
||||
//
|
||||
// Created by Aparajita on 4/9/13.
|
||||
//
|
||||
//
|
||||
|
||||
#include "UserDefaults.h"
|
||||
|
||||
|
||||
NSString * const kDefaultLastEventId = @"lastEventId";
|
||||
NSString * const kDefaultFirstLaunch = @"FirstLaunch";
|
||||
NSString * const kDefaultFirstLaunchVersion = @"firstLaunchVersion";
|
||||
NSString * const kDefaultXCCAPIMode = @"XCCAPIMode";
|
||||
NSString * const kDefaultXCCReactToInodeMod = @"XCCReactMode";
|
||||
NSString * const kDefaultXCCReopenLastProject = @"XCCReopenLastProject";
|
||||
NSString * const kDefaultXCCAutoOpenErrorsPanelOnWarnings = @"XCCAutoOpenErrorsPanelOnWarnings";
|
||||
NSString * const kDefaultXCCAutoOpenErrorsPanelOnErrors = @"XCCAutoOpenErrorsPanelOnErrors";
|
||||
NSString * const kDefaultXCCProjectHistory = @"XCCProjectHistory";
|
||||
NSString * const kDefaultLastOpenedPath = @"LastOpenedPath";
|
||||
NSString * const kDefaultPathModificationDates = @"pathModificationDates";
|
||||
NSString * const kDefaultMaxRecentProjects = @"maxRecentProjects";
|
||||
NSString * const kDefaultLogLevel = @"logLevel";
|
||||
NSString * const kDefaultAutoOpenXcodeProject = @"autoOpenXcodeProject";
|
||||
NSString * const kDefaultShowProcessingNotices = @"showProcessingNotices";
|
||||
@@ -4,4 +4,13 @@
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "DDLog.h"
|
||||
|
||||
#if DEBUG
|
||||
#import "DDTTYLogger.h"
|
||||
#else
|
||||
#import "DDASLLogger.h"
|
||||
#endif
|
||||
|
||||
#import "DDLogLevel.h"
|
||||
#endif
|
||||