New: XcodeCapp 3 total overhaul part 2.
Whew, a complete overhaul of my previous complete overhaul. Changes in no particular order: - Redesigned the About box. - Converted tabs to spaces. - Using Lumberjack for (much better) logging. When debugging, copious logging goes only to Xcode's console, not to the system log. In the release build, only basic info and errors goes to the system log. - Reformatted the help using Pages, it's a PDF now (so I can use better fonts). - Massive optimization across the board. All time-consuming operations are done with NSOperation, which uses Grand Central Dispatch, and can be interrupted. So the XCC menu remains fully responsive during source processing. - Updates to the Xcode project are coalesced so only one read/update/write operation is done. - Instead of launching a shell and reading the .profile, the target executable is directly launched, but it must reside somewhere in the default binary path + /usr/local/bin:/usr/local/narwhal/bin:~/bin. If jsc, python, objj and nib2cib cannot be found in that path, the user is alerted and XCC quits. - Added a preference (true by default) to automatically load the Xcode project when a project is opened. - Added a preference to turn off per-file processing notifications. - Added a hidden preference to set the log level. Useful for debugging a user's release build. - Redesigned the Preferences window. - Renamed some methods/properties, eliminated some unused properties. - All of the windows remember their position. - Moved the fsevent_callback into XcodeCapp.m. - Fixed a bug in mod_pbxproj.py not setting the type of PBXFileReferences which are directories to "folder". - Added support for categories in parser.j. Woo-hoo! - Rewrote pbxprojModifier.py to use a class. - Renamed the Xcode project group names to "Cocoa Classes" and "Cappuccino Source". Within "Cappuccino Source", framework code is kept in a "Frameworks" group. - Shadow filenames are all project-relative now, thus *much* shorter. - pbxprojModifier.py keeps the groups and files in the project in sorted order: Resource folders are at the top, followed by Cocoa Classes, followed by Cappuccino Source. Within Cocoa Classes, non-framework files come first, followed by framework files, followed by xcc_general_include.h. - If either .XcodeSupport or the .xcodeproj is missing, the other is regenerated to ensure they stay in sync. - A compatibility version for .XcodeSupport is stored inside it in Info.plist. If that version < the app's compatibility version, the project is reset. This ensures that format changes in the future will not result in projects in an unknown state. - If the Xcode project cannot be opened, the user is alerted and given the option of regenerating the project. - Resetting the project deletes all .cibs to force the .xibs to be regenerated. - All possible FSEvents are dealt with individually now, and in a way that (hopefully) maintains sync between Cappuccino and XCC. - A file descriptor to the Xcode project is kept open so that If the project path changes it can be relocated. If it does move, the user is alerted ad given the option of reloading the project or quitting. - If one of the watched paths changes, the project is reloaded. - Xcode creates temporary files, they are properly filtered out now when handling FSEvents. - NSRegularExpression was being used before, but that is OS X 10.7 only. NSPredicate is used instead now. -
@@ -18,8 +18,9 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <CoreServices/CoreServices.h>
|
||||
#import <Quartz/Quartz.h>
|
||||
|
||||
#import "TNXcodeCapp.h"
|
||||
@class XcodeCapp;
|
||||
|
||||
@interface AppController : NSObject <NSApplicationDelegate, NSMenuDelegate>
|
||||
|
||||
@@ -32,16 +33,18 @@
|
||||
@property (strong) IBOutlet NSWindow *preferencesWindow;
|
||||
|
||||
@property (strong) IBOutlet NSWindow *helpWindow;
|
||||
@property (assign) IBOutlet NSTextView *helpTextView;
|
||||
@property (assign) IBOutlet PDFView *helpView;
|
||||
|
||||
@property (strong) IBOutlet NSUserDefaultsController *preferencesController;
|
||||
@property (strong) IBOutlet TNXcodeCapp *xcc;
|
||||
@property (strong) IBOutlet NSUserDefaultsController *preferencesController;
|
||||
@property (strong) IBOutlet XcodeCapp *xcc;
|
||||
|
||||
+ (AppController *)sharedAppController;
|
||||
|
||||
- (IBAction)listenToProject:(id)aSender;
|
||||
- (IBAction)loadProject:(id)aSender;
|
||||
- (IBAction)openHelp:(id)aSender;
|
||||
- (IBAction)openAbout:(id)aSender;
|
||||
|
||||
- (void)loadProjectAtPath:(NSString *)path;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -20,24 +20,22 @@
|
||||
#import <AppKit/NSApplication.h>
|
||||
|
||||
#import "AppController.h"
|
||||
#import "TNXcodeCapp.h"
|
||||
#import "Notifications.h"
|
||||
#import "XcodeCapp.h"
|
||||
#import "UserDefaults.h"
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
|
||||
AppController *SharedAppControllerInstance = nil;
|
||||
|
||||
|
||||
@interface AppController ()
|
||||
|
||||
@property BOOL supportsFileModeListening;
|
||||
@property (nonatomic) NSImage *iconActive;
|
||||
@property (nonatomic) NSImage *iconInactive;
|
||||
@property (nonatomic) NSImage *iconWorking;
|
||||
@property (nonatomic) NSImage *iconError;
|
||||
@property (nonatomic) NSMenu *recentMenu;
|
||||
@property NSStatusItem *statusItem;
|
||||
@property (nonatomic) NSImage *iconActive;
|
||||
@property (nonatomic) NSImage *iconInactive;
|
||||
@property (nonatomic) NSImage *iconWorking;
|
||||
@property (nonatomic) NSImage *iconError;
|
||||
@property (nonatomic) NSMenu *recentMenu;
|
||||
@property NSStatusItem *statusItem;
|
||||
|
||||
@end
|
||||
|
||||
@@ -52,18 +50,22 @@ AppController *SharedAppControllerInstance = nil;
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
{
|
||||
SharedAppControllerInstance = self;
|
||||
|
||||
[self registerDefaults];
|
||||
|
||||
[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.helpTextView.textContainerInset = NSMakeSize(10.0, 10.0);
|
||||
|
||||
self.aboutWindow.backgroundColor = [NSColor whiteColor];
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
@@ -83,10 +85,9 @@ AppController *SharedAppControllerInstance = nil;
|
||||
|
||||
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
|
||||
|
||||
[defaultCenter addObserver:self selector:@selector(XcodeCappConversionDidStart:) name:XCCConversionDidStartNotification object:self.xcc];
|
||||
[defaultCenter addObserver:self selector:@selector(XcodeCappConversionDidStop:) name:XCCConversionDidStopNotification object:self.xcc];
|
||||
[defaultCenter addObserver:self selector:@selector(XcodeCappDidPopulateProject:) name:XCCDidPopulateProjectNotification object:self.xcc];
|
||||
[defaultCenter addObserver:self selector:@selector(XcodeCappListeningDidStart:) name:XCCListeningDidStartNotification object:self.xcc];
|
||||
[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];
|
||||
@@ -94,37 +95,77 @@ AppController *SharedAppControllerInstance = nil;
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification
|
||||
{
|
||||
[self.xcc start];
|
||||
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
|
||||
Register default values for preferences
|
||||
*/
|
||||
- (void)registerDefaults
|
||||
- (void)registerDefaultPreferences
|
||||
{
|
||||
NSDictionary *appDefaults = @{
|
||||
kDefaultLastEventId: [NSNumber numberWithUnsignedLongLong:kFSEventStreamEventIdSinceNow],
|
||||
kDefaultFirstLaunch: @YES,
|
||||
kDefaultFirstLaunchVersion: @2.0,
|
||||
kDefaultXCCAPIMode: [NSNumber numberWithInt:kXCCAPIModeAuto],
|
||||
kDefaultXCCReactMode: @YES,
|
||||
kDefaultXCCReopenLastProject: @YES,
|
||||
kDefaultXCCAutoOpenErrorsPanelOnWarnings: @YES,
|
||||
kDefaultXCCAutoOpenErrorsPanelOnErrors: @YES,
|
||||
kDefaultXCCProjectHistory: [NSArray new],
|
||||
kDefaultMaxRecentProjects: @20
|
||||
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 registerDefaults:appDefaults];
|
||||
|
||||
[defaults addObserver:self
|
||||
[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];
|
||||
@@ -134,7 +175,7 @@ AppController *SharedAppControllerInstance = nil;
|
||||
for (NSInteger i = projectHistory.count - 1; i >= 0; --i)
|
||||
{
|
||||
if (![fm fileExistsAtPath:projectHistory[i]])
|
||||
[projectHistory removeObjectAtIndex:i];
|
||||
[projectHistory removeObjectAtIndex:i];
|
||||
}
|
||||
|
||||
NSInteger maxProjects = [defaults integerForKey:kDefaultMaxRecentProjects];
|
||||
@@ -186,77 +227,45 @@ AppController *SharedAppControllerInstance = nil;
|
||||
_recentMenu = [NSMenu new];
|
||||
_recentMenu.delegate = self;
|
||||
self.menuItemHistory.submenu = _recentMenu;
|
||||
}
|
||||
}
|
||||
|
||||
return _recentMenu;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Notification handlers
|
||||
|
||||
/*!
|
||||
Clean up when the application stops.
|
||||
It will stop the FSEvent listener, and store the last event id.
|
||||
*/
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app
|
||||
- (void)batchDidStart:(NSNotification *)note
|
||||
{
|
||||
[self.xcc stop];
|
||||
DDLogVerbose(@"Batch start");
|
||||
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
- (void)XcodeCappConversionDidStart:(NSNotification *)aNotification
|
||||
{
|
||||
self.statusItem.image = self.iconWorking;
|
||||
}
|
||||
|
||||
- (void)XcodeCappConversionDidStop:(NSNotification *)aNotification
|
||||
- (void)batchDidEnd:(NSNotification *)note
|
||||
{
|
||||
DDLogVerbose(@"Batch end");
|
||||
|
||||
if (!self.xcc.isLoadingProject)
|
||||
self.statusItem.image = self.xcc.hasErrors ? self.iconError : self.iconActive;
|
||||
}
|
||||
|
||||
- (void)XcodeCappDidPopulateProject:(NSNotification *)aNotification
|
||||
{
|
||||
}
|
||||
|
||||
- (void)XcodeCappListeningDidStart:(NSNotification *)aNotification
|
||||
- (void)projectDidFinishLoading:(NSNotification *)note
|
||||
{
|
||||
self.statusItem.image = self.xcc.hasErrors ? self.iconError : self.iconActive;
|
||||
self.menuItemListen.title = [NSString stringWithFormat:@"Close “%@”", self.xcc.currentProjectPath.lastPathComponent];
|
||||
self.menuItemListen.action = @selector(stopListening:);
|
||||
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])
|
||||
if ([keyPath isEqualToString:kDefaultMaxRecentProjects])
|
||||
[self pruneProjectHistory];
|
||||
}
|
||||
|
||||
#pragma mark - Utilities
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)listenToProject:(id)aSender
|
||||
- (IBAction)loadProject:(id)aSender
|
||||
{
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
|
||||
@@ -270,57 +279,23 @@ AppController *SharedAppControllerInstance = nil;
|
||||
return;
|
||||
|
||||
NSString *projectPath = [[openPanel.URLs[0] path] stringByStandardizingPath];
|
||||
[self listenToProjectAtPath:projectPath];
|
||||
[self loadProjectAtPath:projectPath];
|
||||
}
|
||||
|
||||
- (void)listenToProjectAtPath:(NSString *)path
|
||||
{
|
||||
[self stopListening: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 listenToProjectAtPath:path];
|
||||
}
|
||||
|
||||
- (void)stopListening:(id)aSender
|
||||
- (void)closeProject:(id)aSender
|
||||
{
|
||||
[self.xcc stop];
|
||||
|
||||
self.statusItem.image = self.iconInactive;
|
||||
self.menuItemListen.title = @"Open Project…";
|
||||
self.menuItemListen.action = @selector(listenToProject:);
|
||||
self.menuItemListen.action = @selector(loadProject:);
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kDefaultLastOpenedPath];
|
||||
}
|
||||
|
||||
- (void)switchToProject:(id)aSender
|
||||
- (void)switchToProject:(NSMenuItem *)aSender
|
||||
{
|
||||
[self listenToProjectAtPath:[aSender representedObject]];
|
||||
[self loadProjectAtPath:aSender.representedObject];
|
||||
}
|
||||
|
||||
- (void)clearProjectHistory:(id)aSender
|
||||
@@ -331,41 +306,42 @@ AppController *SharedAppControllerInstance = nil;
|
||||
|
||||
- (IBAction)openHelp:(id)aSender
|
||||
{
|
||||
[self.helpTextView readRTFDFromFile:[[NSBundle mainBundle] pathForResource:@"help" ofType:@"rtfd"]];
|
||||
|
||||
[self openWindow:self.helpWindow centered:YES];
|
||||
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 centered:YES];
|
||||
[self openWindow:self.aboutWindow];
|
||||
}
|
||||
|
||||
- (IBAction)openPreferences:(id)aSender
|
||||
{
|
||||
[self openWindow:self.preferencesWindow centered:NO];
|
||||
[self openWindow:self.preferencesWindow];
|
||||
}
|
||||
|
||||
- (void)openWindow:(NSWindow *)aWindow centered:(BOOL)shouldBeCentered
|
||||
{
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
|
||||
if (shouldBeCentered)
|
||||
[aWindow center];
|
||||
|
||||
[aWindow makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
|
||||
#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.currentProjectPath;
|
||||
return !!self.xcc.projectPath;
|
||||
}
|
||||
else if (menu == self.recentMenu)
|
||||
{
|
||||
@@ -385,11 +361,74 @@ AppController *SharedAppControllerInstance = nil;
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Private Helpers
|
||||
#pragma mark - Bindings
|
||||
|
||||
- (NSString *)bundleVersion
|
||||
{
|
||||
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
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
|
||||
@@ -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,115 +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"
|
||||
|
||||
void fsevents_callback(ConstFSEventStreamRef streamRef,
|
||||
void *userData,
|
||||
size_t numEvents,
|
||||
void *eventPaths,
|
||||
const FSEventStreamEventFlags eventFlags[],
|
||||
const FSEventStreamEventId eventIds[])
|
||||
{
|
||||
TNXcodeCapp *xcc = (__bridge TNXcodeCapp *)userData;
|
||||
NSArray *paths = (__bridge NSArray *)eventPaths;
|
||||
BOOL usingFileBasedListening = [xcc supportsFileBasedListening];
|
||||
|
||||
for (size_t i = 0; i < numEvents; ++i)
|
||||
{
|
||||
[xcc updateLastEventId:eventIds[i]];
|
||||
|
||||
FSEventStreamEventFlags flags = eventFlags[i];
|
||||
NSString *path = [[paths objectAtIndex:i] stringByStandardizingPath];
|
||||
|
||||
if (usingFileBasedListening)
|
||||
{
|
||||
if ([xcc pathMatchesIgnoredPaths:path])
|
||||
continue;
|
||||
|
||||
if ((flags & kFSEventStreamEventFlagItemIsFile) &&
|
||||
!([xcc isXibFile:path] || [xcc isObjjFile:path] || [xcc isXCCIgnoreFile:path]))
|
||||
{
|
||||
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 tidy up the project when we receive
|
||||
// an event.
|
||||
[xcc tidyShadowedFiles];
|
||||
|
||||
BOOL isDirectory = NO;
|
||||
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory];
|
||||
|
||||
if (isDirectory)
|
||||
continue;
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
DLog(@"File removed: %@", path);
|
||||
[xcc handleFileRemovalAtPath:path];
|
||||
}
|
||||
else
|
||||
{
|
||||
DLog(@"File modified/added: %@", path);
|
||||
[xcc handleFileModificationAtPath:path notify:YES];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We should drop support for Snow Leopard soon.
|
||||
|
||||
BOOL isDirectory = NO;
|
||||
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory];
|
||||
|
||||
// If for some reasons the path is not a directory,
|
||||
// we don't want to deal with it in this mode.
|
||||
if (!isDirectory)
|
||||
continue;
|
||||
|
||||
[xcc tidyShadowedFiles];
|
||||
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
NSArray *subpaths = [fm contentsOfDirectoryAtPath:path error:NULL];
|
||||
|
||||
for (NSString *subpath in subpaths)
|
||||
{
|
||||
NSString *fullPath = [path stringByAppendingPathComponent:subpath];
|
||||
|
||||
if ([xcc pathMatchesIgnoredPaths:fullPath] ||
|
||||
!([xcc isXibFile:fullPath] || [xcc isObjjFile:fullPath] || [xcc isXCCIgnoreFile:fullPath]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NSDate *lastModifiedDate = [xcc lastModificationDateForPath:fullPath];
|
||||
NSDictionary *fileAttributes = [fm attributesOfItemAtPath:fullPath error:nil];
|
||||
NSDate *fileModDate = [fileAttributes objectForKey:NSFileModificationDate];
|
||||
|
||||
if ([fileModDate compare:lastModifiedDate] == NSOrderedDescending)
|
||||
{
|
||||
[xcc updateLastModificationDate:fileModDate forPath:fullPath];
|
||||
[xcc handleFileModificationAtPath:fullPath notify:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[xcc updateUserDefaultsWithLastEventId];
|
||||
}
|
||||
@@ -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,165 @@
|
||||
//
|
||||
// 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])
|
||||
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])
|
||||
{
|
||||
NSDictionary *info =
|
||||
@{
|
||||
@"projectId":self.projectId,
|
||||
@"sourcePath":realPath,
|
||||
@"projectPath":fullProjectPath
|
||||
};
|
||||
|
||||
if (self.isCancelled)
|
||||
return;
|
||||
|
||||
[center postNotificationName:XCCNeedSourceToProjectPathMappingNotification object:self userInfo:info];
|
||||
}
|
||||
}
|
||||
|
||||
[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
|
||||
@@ -30,5 +30,7 @@
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>XCCCompatibilityVersion</key>
|
||||
<real>3</real>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -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
|
||||
@@ -1,390 +0,0 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf370
|
||||
{\fonttbl\f0\fnil\fcharset0 LucidaGrande;\f1\fswiss\fcharset0 Helvetica;\f2\fmodern\fcharset0 Courier;
|
||||
}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid1\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1}
|
||||
{\list\listtemplateid2\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid101\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid2}
|
||||
{\list\listtemplateid3\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid201\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid3}
|
||||
{\list\listtemplateid4\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid301\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid4}
|
||||
{\list\listtemplateid5\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid401\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid5}
|
||||
{\list\listtemplateid6\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid501\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid6}
|
||||
{\list\listtemplateid7\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid601\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid7}
|
||||
{\list\listtemplateid8\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid701\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid8}
|
||||
{\list\listtemplateid9\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid801\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid9}
|
||||
{\list\listtemplateid10\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid901\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid10}
|
||||
{\list\listtemplateid11\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid1001\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid11}
|
||||
{\list\listtemplateid12\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid1101\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid12}}
|
||||
{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}{\listoverride\listid4\listoverridecount0\ls4}{\listoverride\listid5\listoverridecount0\ls5}{\listoverride\listid6\listoverridecount0\ls6}{\listoverride\listid7\listoverridecount0\ls7}{\listoverride\listid8\listoverridecount0\ls8}{\listoverride\listid9\listoverridecount0\ls9}{\listoverride\listid10\listoverridecount0\ls10}{\listoverride\listid11\listoverridecount0\ls11}{\listoverride\listid12\listoverridecount0\ls12}}
|
||||
\margl1440\margr1440\vieww16380\viewh15560\viewkind0
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0\b\fs36 \cf0 XcodeCapp Help
|
||||
\b0 \
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\b\fs28 \cf0 What is XcodeCapp?\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\b0\fs36 \cf0 \
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\fs24 \cf0 One of Cappuccino\'92s greatest features is its ability to use Xcode 4 to create the user interface for your web applications. Xcode creates .xib files which then must be converted by the command line utility
|
||||
\b nib2cib
|
||||
\b0 to .cib files for use with Cappuccino. But beginning with Xcode 4, there is no way to directly create outlets and actions without editing Objective-C header files.\
|
||||
\
|
||||
XcodeCapp acts as a bridge between Xcode and Cappuccino. It performs two main functions:\
|
||||
\
|
||||
\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural
|
||||
\ls1\ilvl0\cf0 {\listtext \uc0\u9642 }Creates an Xcode project where you can edit your Cappuccino project\'92s xibs.\
|
||||
{\listtext \uc0\u9642 }Reads your source files and automatically creates outlets and actions in the xib file.\
|
||||
{\listtext \uc0\u9642 }Automatically converts xib files to cib files when the .xib file is modified.\
|
||||
{\listtext \uc0\u9642 }Listens for changes to source files and xibs and updates outlets and actions in the xib file accordingly.\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\b\fs28 \cf0 Using XcodeCapp
|
||||
\b0 \
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\fs24 \cf0 \
|
||||
XcodeCapp is very easy to use and requires very little user interaction. When you build Cappuccino with jake, it will create a symlink to the XcodeCapp application in your Applications folder. Launch XcodeCapp from there and the XcodeCapp icon will appear in your menu bar. Clicking on the icon will display the XcodeCapp menu:\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f1 \cf0 {{\NeXTGraphic menu.png \width5000 \height5160
|
||||
}¬}\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
\
|
||||
When the status icon is light gray, XcodeCapp does not have a project open.\
|
||||
\
|
||||
|
||||
\b \
|
||||
Opening a Project\
|
||||
|
||||
\b0 \
|
||||
First you have to tell XcodeCapp what Cappuccino project to open.\
|
||||
\
|
||||
\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural
|
||||
\ls2\ilvl0\cf0 {\listtext 1. }Select \'93Open Project\'85\'94 from the XcodeCapp menu. A folder chooser dialog will appear.\
|
||||
{\listtext 2. }Navigate to the root folder of your project \'97 the one that contains index-debug.html and Jakefile.\
|
||||
{\listtext 3. }Click Open.\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
\cf0 Once you open a project, XcodeCapp does the following:\
|
||||
\
|
||||
\pard\tx220\tx720\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\li720\fi-720\pardirnatural
|
||||
\ls3\ilvl0\cf0 {\listtext 1. }Creates an Xcode project with the name of the project directory, if no such project already exists.\
|
||||
{\listtext 2. }Parses source code in the project directory and all non-Cappuccino subdirectories (such as Build, Frameworks, etc.).\
|
||||
{\listtext 3. }Parses source code in non-Cappuccino frameworks.\
|
||||
{\listtext 4. }If no cib file exists for a given xib file, converts the xib using nib2cib.\
|
||||
\pard\tx560\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
\cf0 \
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
\cf0 When XcodeCapp is processing files, the status icon changes to light blue.\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f1 \cf0 {{\NeXTGraphic loading-project.png \width6560 \height1960
|
||||
}¬}\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
\cf0 \
|
||||
During source parsing, XcodeCapp creates an invisible folder called \'93.XcodeSupport\'94 in the root project directory. This folder contains files created by XcodeCapp that act as the bridge between Cappuccino and Xcode, so you should never directly modify those files. You will probably want to ignore this folder in your IDE and source code management system as well.\
|
||||
\
|
||||
After XcodeCapp has opened a project, it will listen for changes to the following files anywhere in the project:\
|
||||
\
|
||||
\pard\tx220\tx720\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\li720\fi-720\pardirnatural
|
||||
\ls4\ilvl0\cf0 {\listtext \uc0\u9642 }*.xib \'96 Xcode 4 interface builder files\
|
||||
{\listtext \uc0\u9642 }*.j - Objective-J source\
|
||||
{\listtext \uc0\u9642 }.xcodecapp-ignore - Specifies files XcodeCapp should ignore. See \'93Ignoring Files and Folders\'94 below.\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
\cf0 \
|
||||
When XcodeCapp has a project open and is listening for changes, the status icon changes to dark gray.\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f1 \cf0 {{\NeXTGraphic active.png \width660 \height420
|
||||
}¬}\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
\
|
||||
The most recently opened project is inserted at the top of the Open Recent submenu in the XcodeCapp menu. By default, the recent project list is limited to 20 projects. You can change that number in the Preferences window. See \'93Preferences\'94 below for more information.\
|
||||
\
|
||||
If you quit XcodeCapp while a project is open, by default when you reopen XcodeCapp it will reopen the most recently opened project. You can change this behavior in the Preferences window. See \'93Preferences\'94 below for more information.\
|
||||
\
|
||||
\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
|
||||
\b \cf0 Notifications\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
|
||||
\b0 \cf0 \
|
||||
XcodeCapp shows notifications whenever it loads a project or processes files. If you are using Mac OS X 10.8+, notifications will appear in Notification Center. Otherwise they will appear according to Growl\'92s preferences if Growl is installed, or in a simple notification window if Growl is not installed. To turn notifications off, use the Notifications control panel (OS X 10.8+) or Growl preferences.\
|
||||
\
|
||||
Note that notifications remain on screen for a minimum amount of time, and thus may lag behind the actual processing of the files.\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
|
||||
\b \cf0 Handling Errors\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
|
||||
\b0 \cf0 \
|
||||
If an error or warning occurs during the parsing of an Objective-J file or the conversion of a xib, XcodeCapp notifies you in two ways:\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
\pard\tx220\tx720\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\li720\fi-720\pardirnatural
|
||||
\ls5\ilvl0\cf0 {\listtext \uc0\u9642 }If an error occurs, the status icon becomes red.\
|
||||
\pard\tx560\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\sb120\pardirnatural
|
||||
\cf0
|
||||
\f1 {{\NeXTGraphic error.png \width620 \height420
|
||||
}¬}
|
||||
\f0 \
|
||||
\pard\tx560\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\sl300\slmult1\pardirnatural
|
||||
\cf0 \
|
||||
\pard\tx220\tx720\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\li720\fi-720\sl300\slmult1\pardirnatural
|
||||
\ls6\ilvl0\cf0 {\listtext \uc0\u9642 }If an error or warning occurs, by default XcodeCapp will open the Errors & Warnings panel and display information about the error or warning.\
|
||||
\pard\tx560\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\sb120\pardirnatural
|
||||
\cf0
|
||||
\f1 {{\NeXTGraphic errors-panel.png \width11780 \height5900
|
||||
}¬}
|
||||
\f0 \
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
Selecting an error in the list and clicking Open or double-clicking on an error opens the offending file in the preferred editor for that file and closes the Errors panel. In the case of .j files, XcodeCapp will attempt to open the file directly to the offending line in the following editors:\
|
||||
\
|
||||
\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural
|
||||
\ls7\ilvl0\cf0 {\listtext \uc0\u9642 }Sublime Text\
|
||||
{\listtext \uc0\u9642 }TextMate\
|
||||
{\listtext \uc0\u9642 }TextWrangler\
|
||||
{\listtext \uc0\u9642 }BBEdit\
|
||||
{\listtext \uc0\u9642 }Chocolat\
|
||||
{\listtext \uc0\u9642 }MacVIM\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
\cf0 \
|
||||
The error list is cleared when a new project is opened. Also, whenever a file is modified, any existing errors for that file are removed. So the Errors & Warnings panel always represents the current state of your files.\
|
||||
\
|
||||
If you do not wish the Errors panel to open automatically when errors or warnings occur, you can change that behavior in the Preferences window. See \'93Preferences\'94 below for more information.\
|
||||
\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
|
||||
\b \cf0 \
|
||||
Declaring Outlets and Actions\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
|
||||
\b0 \cf0 \
|
||||
The most important function XcodeCapp performs is to create outlets and actions for use with Xcode. You create outlets by prefixing them in your source files with
|
||||
\b @outlet
|
||||
\b0 or
|
||||
\b IBOutlet
|
||||
\b0 . For example, in the class below, there are four outlets defined:\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f1 \cf0 {{\NeXTGraphic outlets.png \width10720 \height4820
|
||||
}¬}\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
\cf0 Similarly, you declare actions by declaring the return type of the method to be
|
||||
\b @action
|
||||
\b0 or
|
||||
\b IBAction
|
||||
\b0 . For example, the following method can serve as an action for a control:\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f1 \cf0 {{\NeXTGraphic actions.png \width10800 \height1580
|
||||
}¬}\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
\cf0 \
|
||||
\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
|
||||
\b \cf0 Editing Xibs\
|
||||
\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
|
||||
\b0 \cf0 When you open a project, XcodeCapp creates an Xcode 4 project with which you to edit your project\'92s xibs. To open the Xcode project, click on the XcodeCapp menu and select \'93Open Project in Xcode\'94.\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f1 \cf0 {{\NeXTGraphic open-project-in-xcode.png \width5900 \height5060
|
||||
}¬}\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
\
|
||||
Once the Xcode project is open, you can edit your xibs with the interface builder. The Xcode project contains three folders by default:\
|
||||
\
|
||||
\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural
|
||||
\ls8\ilvl0\cf0 {\listtext \uc0\u9642 }Resources \'96 Contains the files in your project\'92s Resources directory.\
|
||||
{\listtext \uc0\u9642 }Classes \'96 Contains the Objective-C classes generated by XcodeCapp. You should not edit or remove these.\
|
||||
{\listtext \uc0\u9642 }Sources \'96 Contains the source files found by XcodeCapp. You may edit these files in Xcode if you wish.\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
All of the outlets and actions you declared in your source will be available in interface builder for connection to views. For example, the InfoPanelController shown above would have these outlets in interface builder:\
|
||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||
\cf0 \
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f1 \cf0 {{\NeXTGraphic outlets-xcode.png \width9760 \height5960
|
||||
}¬}\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
\
|
||||
To add more outlets or actions to the xib, you must edit the Cappuccino source and wait briefly while XcodeCapp processes the changes; do not attempt to add an outlet or action in Xcode.\
|
||||
\
|
||||
\
|
||||
|
||||
\b Working with User Frameworks\
|
||||
\
|
||||
|
||||
\b0 When a project is opened, XcodeCapp looks for user (non-Cappuccino) frameworks in your project\'92s Frameworks/Debug directory. Any debug user frameworks in source form will be processed by XcodeCapp. This allows you to declare outlets and actions in frameworks and use them within Xcode. In addition, if a framework contains a Resources directory, a \'93<framework> Resources\'94 folder referencing that directory is created in the Xcode project. This allows you to reference framework resources within Xcode.\
|
||||
\
|
||||
If you want your user frameworks to execute in compiled form, but you still want XcodeCapp to have access to the framework\'92s source in order to parse outlets and actions, do the following:\
|
||||
\
|
||||
\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural
|
||||
\ls9\ilvl0\cf0 {\listtext 1. }Copy or symlink to the compiled framework as usual within your project\'92s Frameworks directory.\
|
||||
{\listtext 2. }Create a directory called \'93Source\'94 within your project\'92s Frameworks directory.\
|
||||
{\listtext 3. }Within the Source directory, copy or symlink to the framework source directory.\
|
||||
{\listtext 4. }XcodeCapp will recursively search the Frameworks/Source directory when a project is opened.\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
For example, here is the structure of a project\'92s Frameworks directory:\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f1 \cf0 {{\NeXTGraphic frameworks.png \width6320 \height7060
|
||||
}¬}\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
\
|
||||
Note the following:\
|
||||
\
|
||||
\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural
|
||||
\ls10\ilvl0\cf0 {\listtext \uc0\u9642 }Debug/JQueryFileUpload is a symlink to the compiled framework. This is what will be executed when the app is run in debug mode.\
|
||||
{\listtext \uc0\u9642 }The Source directory contains a symlink to the JQueryFileUpload source. This will be parsed by XcodeCapp.\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
If you need to use image resources located in a framework \'97 for example in an NSButton or NSImageView \'97 you have two choices:\
|
||||
\
|
||||
\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural
|
||||
\ls11\ilvl0\cf0 {\listtext 1. }Use the base image name alone, for example \'93cancel\'94, which you wish to represent the image \'93cancel.png\'94 in the framework \'93JQueryFileUpload\'94.\
|
||||
{\listtext 2. }Use a full name@framework specification, for example \'93cancel@ JQueryFileUpload\'94 (without the quotes).\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 In the first form, during conversion, nib2cib will first search the app\'92s Resources directory for an image with the given name, then it will search all of the Resources directories of the debug frameworks, followed by the release frameworks. The first match wins, and if the image was found in a framework, the framework\'92s bundle identifier is recorded so the image can be loaded from the correct bundle at runtime.\
|
||||
\
|
||||
Since there might be multiple images in the search path that have the same name, if you want to ensure you are using an image from a specific framework, use the second form. During conversion, nib2cib will check the named framework for the given image, and if it is found the framework\'92s bundle identifier is recorded so the image can be loaded from the correct bundle at runtime.\
|
||||
\
|
||||
\
|
||||
|
||||
\b Ignoring Files and Folders\
|
||||
\
|
||||
|
||||
\b0 There may be source which you do not want XcodeCapp to process. To specify directories or files which you want XcodeCapp to ignore, do the following:\
|
||||
\
|
||||
\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural
|
||||
\ls12\ilvl0\cf0 {\listtext 1. }Create a text file in the root of the project directory named \'93.xcodecapp-ignore\'94.\
|
||||
{\listtext 2. }Enter one line for each pattern you would like to ignore, where a pattern matches the
|
||||
\b absolute
|
||||
\b0 (full) path to the file or directory.\
|
||||
{\listtext 3. }Save the file. Whenever .xcodecapp-ignore is modified, XcodeCapp will parse it to rebuild the ignore list.\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
\cf0 \
|
||||
Patterns may use \'93*\'94 as a wildcard to match zero or more characters (it is
|
||||
\b not
|
||||
\b0 a shell glob) and are matched against the
|
||||
\b absolute
|
||||
\b0 path of the file or directory, so in most cases your patterns should begin with \'93*\'94. To ignore a directory, add \'93/\'94 at the end of the directory name. For example, \'93*/server/\'94 will ignore that directory and any files within it.\
|
||||
\
|
||||
For example, to ignore the \'93Modules\'94 directory and the xib file \'93foo.xib\'94 in your project, enter these lines in .xcodecapp-ignore:\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f2 \cf0 */Modules/\
|
||||
*/foo.xib\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
Sometimes you may want to ignore almost all of the files or directories within a given directory. In such cases you can use an include pattern. An include pattern has the same syntax as the normal exclude patterns, but begins with \'93!\'94. Since ignore patterns are parsed in the order they appear in .xcodecapp-ignore, an include pattern should come after the exclude pattern it overrides. For example, if you wanted to exclude everything in the Modules directory, except for the Classes directory and any xib files, you would use the following patterns:\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f2 \cf0 */Modules/\
|
||||
!*/Modules/Classes/\
|
||||
!*/Modules/*.xib
|
||||
\f0 \
|
||||
\
|
||||
Note that in addition to whatever patterns you specify (if any) in .xcodecapp-ignore, XcodeCapp
|
||||
\b always
|
||||
\b0 parses the following patterns
|
||||
\b before
|
||||
\b0 your patterns:\
|
||||
\
|
||||
|
||||
\f2 */Frameworks/\
|
||||
!*/Frameworks/Debug/\
|
||||
*/AppKit/\
|
||||
*/Foundation/\
|
||||
*/Objective-J/\
|
||||
*/*.environment/\
|
||||
*/Build/\
|
||||
*/.*/\
|
||||
*/NS_*.j\
|
||||
*/main.j\
|
||||
*/.*\
|
||||
!*/.xcodecapp-ignore\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\b\fs28 \cf0 Preferences\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\fs24 \cf0 \
|
||||
|
||||
\b0 XcodeCapp supports the following preferences:\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f1 \cf0 {{\NeXTGraphic preferences.png \width6840 \height3560
|
||||
}¬}\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\f0 \cf0 \
|
||||
\
|
||||
Currently the only warnings generated by XcodeCapp are from using a category in an Objective-J file. Since this warning does not prevent the rest of the file from being parsed, you may choose not to display the Errors panel when category warnings are generated.\
|
||||
\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\b\fs28 \cf0 Troubleshooting\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\b0\fs24 \cf0 Ultimately XcodeCapp is a bit of a hack \'97 an elegant hack, but a hack nonetheless. It is doing all kinds of processing behind Xcode\'92s back, so to speak, and occasionally Xcode may get out of sync with your source files. In such cases, you can reset XcodeCapp by selecting \'93Reset Project\'94 from the XcodeCapp menu. This will remove and then regenerate the Xcode project and .XcodeSupport folder. If the Xcode project is open in Xcode it will be closed before being removed. \
|
||||
\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\b\fs28 \cf0 Credits\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\b0\fs24 \cf0 XcodeCapp was originally written by Francisco Tolmasky.\
|
||||
The Cocoa port was written by Antoine Mercadal and Aparajita Fishman.\
|
||||
XcodeCapp (Cocoa version, formerly known as XcodeCapp-cocoa) was originally built for the Archipel Project (archipelproject.org) and is now part of Cappuccino.}
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 25 KiB |
@@ -169,20 +169,24 @@ class PBXFileReference(PBXType):
|
||||
}
|
||||
|
||||
trees = [
|
||||
'<absolute>',
|
||||
'<group>',
|
||||
'BUILT_PRODUCTS_DIR',
|
||||
'DEVELOPER_DIR',
|
||||
'SDKROOT',
|
||||
'SOURCE_ROOT',
|
||||
]
|
||||
'<absolute>',
|
||||
'<group>',
|
||||
'BUILT_PRODUCTS_DIR',
|
||||
'DEVELOPER_DIR',
|
||||
'SDKROOT',
|
||||
'SOURCE_ROOT',
|
||||
]
|
||||
|
||||
def guess_file_type(self):
|
||||
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
|
||||
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
|
||||
@@ -530,7 +534,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)
|
||||
@@ -594,7 +598,7 @@ class XcodeProject(PBXDict):
|
||||
if b.add_library_search_paths(paths, recursive):
|
||||
self.modified = True
|
||||
|
||||
# TODO: need to return value if project has been modified
|
||||
# TODO: need to return value if project has been modified
|
||||
|
||||
def get_obj(self, id):
|
||||
return self.objects.get(id)
|
||||
@@ -604,36 +608,36 @@ class XcodeProject(PBXDict):
|
||||
|
||||
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('sourceTree') == tree]
|
||||
and f.get('path') == os_path
|
||||
and f.get('sourceTree') == tree]
|
||||
|
||||
return files
|
||||
|
||||
def get_files_by_name(self, name, parent=None):
|
||||
if parent:
|
||||
files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference'
|
||||
and f.get(name) == name
|
||||
and parent.has_child(f)]
|
||||
and f.get(name) == name
|
||||
and parent.has_child(f)]
|
||||
else:
|
||||
files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference'
|
||||
and f.get(name) == name]
|
||||
and f.get(name) == name]
|
||||
|
||||
return files
|
||||
|
||||
def get_build_files(self, id):
|
||||
files = [f for f in self.objects.values() if f.get('isa') == 'PBXBuildFile'
|
||||
and f.get('fileRef') == id]
|
||||
and f.get('fileRef') == id]
|
||||
|
||||
return files
|
||||
|
||||
def get_groups_by_name(self, name, parent=None):
|
||||
if parent:
|
||||
groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'
|
||||
and g.get_name() == name
|
||||
and parent.has_child(g)]
|
||||
and g.get_name() == name
|
||||
and parent.has_child(g)]
|
||||
else:
|
||||
groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'
|
||||
and g.get_name() == name]
|
||||
and g.get_name() == name]
|
||||
|
||||
return groups
|
||||
|
||||
@@ -666,7 +670,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
|
||||
|
||||
@@ -816,12 +820,12 @@ class XcodeProject(PBXDict):
|
||||
results.append(build_file)
|
||||
|
||||
if abs_path and tree == 'SOURCE_ROOT' and os.path.isfile(abs_path)\
|
||||
and file_ref.build_phase == 'PBXFrameworksBuildPhase':
|
||||
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)
|
||||
|
||||
if abs_path and tree == 'SOURCE_ROOT' and not os.path.isfile(abs_path)\
|
||||
and file_ref.build_phase == 'PBXFrameworksBuildPhase':
|
||||
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)
|
||||
@@ -1039,11 +1043,11 @@ class XcodeProject(PBXDict):
|
||||
|
||||
for f in v:
|
||||
filerefs.extend([fr.id for fr in self.objects.values() if fr.get('isa') == 'PBXFileReference'
|
||||
and fr.get('name') == f])
|
||||
and fr.get('name') == f])
|
||||
|
||||
|
||||
buildfiles = [bf for bf in self.objects.values() if bf.get('isa') == 'PBXBuildFile'
|
||||
and bf.get('fileRef') in filerefs]
|
||||
and bf.get('fileRef') in filerefs]
|
||||
|
||||
for bf in buildfiles:
|
||||
if bf.add_compiler_flag(k):
|
||||
@@ -1153,22 +1157,22 @@ class XcodeProject(PBXDict):
|
||||
#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)]
|
||||
('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]) == None):
|
||||
@@ -1226,7 +1230,7 @@ class XcodeProject(PBXDict):
|
||||
out.write('"'+XcodeProject.addslashes(root)+'"')
|
||||
if(root in self.uuids):
|
||||
out.write(" /* "+self.uuids[root]+" */");
|
||||
|
||||
|
||||
@classmethod
|
||||
def getJSONFromXML(cls, root):
|
||||
result = ''
|
||||
@@ -1252,7 +1256,7 @@ class XcodeProject(PBXDict):
|
||||
for child in root.childNodes:
|
||||
if child.nodeType != Node.ELEMENT_NODE:
|
||||
continue;
|
||||
|
||||
|
||||
if(i>0):
|
||||
result += ","
|
||||
result += XcodeProject.getJSONFromXML(child);
|
||||
@@ -1266,24 +1270,24 @@ class XcodeProject(PBXDict):
|
||||
break
|
||||
result += data
|
||||
return result;
|
||||
|
||||
|
||||
@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'
|
||||
return None
|
||||
|
||||
|
||||
# load project by converting to JSON and parse
|
||||
p = subprocess.Popen([XcodeProject.plutil_path, '-convert', 'xml1', '-o', '-', path], stdout=subprocess.PIPE)
|
||||
rawXML = p.communicate()[0]
|
||||
|
||||
|
||||
xml = parseString(rawXML);
|
||||
jsonStr = XcodeProject.getJSONFromXML(xml.getElementsByTagName('dict')[0]);
|
||||
|
||||
|
||||
tree = json.loads(jsonStr)
|
||||
return XcodeProject(tree, path)
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
var FILE = require("file"),
|
||||
OS = require("os"),
|
||||
stream = require("narwhal/term").stream,
|
||||
|
||||
SLASH_REPLACEMENT = "∕"; // DIVISION SLASH, Unicode: U+2215
|
||||
|
||||
@@ -46,22 +47,12 @@ var errors = [],
|
||||
{
|
||||
ClassDeclarationStatement: function(node, st, c)
|
||||
{
|
||||
if (node.categoryname)
|
||||
{
|
||||
[errors addObject:@{
|
||||
@"message": "Categories are not supported yet, ignoring it.",
|
||||
@"path": node.loc.source,
|
||||
@"line": node.loc.start.line
|
||||
}];
|
||||
|
||||
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": [],
|
||||
@@ -101,28 +92,31 @@ var errors = [],
|
||||
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)
|
||||
raise(node.loc.start, "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 named '" + selector + "' is declared multiple times.");
|
||||
|
||||
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)
|
||||
raise(node.loc.start, "Method '" + selector + "' is an action but has more than one parameter.");
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -137,25 +131,32 @@ function compile(node, state, visitor)
|
||||
c(node, state);
|
||||
};
|
||||
|
||||
function shadowBaseNameForPath(path)
|
||||
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)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileURL = new CFURL(args[1]),
|
||||
outputDirectory = args[2],
|
||||
baseFilename = shadowBaseNameForPath(fileURL.path()),
|
||||
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(fileURL, { charset: "UTF-8" }),
|
||||
source = FILE.read(sourcePath, { charset: "UTF-8" }),
|
||||
flags = ObjectiveJ.Preprocessor.Flags.IncludeDebugSymbols | ObjectiveJ.Preprocessor.Flags.IncludeTypeSignatures,
|
||||
sourceFile = fileURL.path(),
|
||||
tokens = ObjectiveJ.acorn.parse(source, { locations:true, sourceFile:sourceFile }),
|
||||
tokens = ObjectiveJ.acorn.parse(source, { locations:true, sourceFile:sourcePath }),
|
||||
classesInformation = [],
|
||||
ObjectiveCSource = "",
|
||||
ObjectiveCHeader = "",
|
||||
@@ -166,7 +167,6 @@ function main(args)
|
||||
// dump(classesInformation)
|
||||
|
||||
ObjectiveCHeader +=
|
||||
"#import <Foundation/Foundation.h>\n" +
|
||||
"#import <Cocoa/Cocoa.h>\n" +
|
||||
'#import "xcc_general_include.h"\n';
|
||||
|
||||
@@ -176,7 +176,10 @@ function main(args)
|
||||
classesInformation.forEach(function(aClass)
|
||||
{
|
||||
// add new class definition
|
||||
ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ : %@", aClass.name, NSCompatibleClassName(aClass.superClass)];
|
||||
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)
|
||||
@@ -215,10 +218,10 @@ function main(args)
|
||||
{
|
||||
[errors addObject:@{
|
||||
@"message": e.message,
|
||||
@"path": sourceFile,
|
||||
@"path": sourcePath,
|
||||
@"line": e.line
|
||||
}];
|
||||
|
||||
|
||||
hasErrors = YES;
|
||||
}
|
||||
|
||||
@@ -226,8 +229,8 @@ function main(args)
|
||||
{
|
||||
var plist = [CPPropertyListSerialization dataFromPropertyList:errors format:CPPropertyListXMLFormat_v1_0];
|
||||
|
||||
print([plist rawString]);
|
||||
|
||||
stream.printError([plist rawString]);
|
||||
|
||||
// If there were category warnings, hasErrors is NO, so return a warning status
|
||||
OS.exit(hasErrors ? 1 : 2);
|
||||
}
|
||||
@@ -567,6 +570,7 @@ var NSClasses = {
|
||||
"NSStepper" : YES,
|
||||
"NSStepperCell" : YES,
|
||||
"NSString Application Kit Additions" : YES,
|
||||
"NSTableCellView" : YES,
|
||||
"NSTableColumn" : YES,
|
||||
"NSTableHeaderCell" : YES,
|
||||
"NSTableHeaderView" : YES,
|
||||
|
||||
@@ -4,135 +4,254 @@ import re
|
||||
import sys
|
||||
from mod_pbxproj import XcodeProject
|
||||
|
||||
XCODE_SUPPORT_FOLDER = ".XcodeSupport"
|
||||
SLASH_REPLACEMENT = u"∕" # DIVISION SLASH Unicode U+2215
|
||||
STRING_RE = re.compile(ur"^\s*<string>(.*)</string>\s*$", re.MULTILINE)
|
||||
FRAMEWORKS_RE = re.compile(ur"^(.+/Frameworks/(?:Debug|Source)/([^/]+))/.+$")
|
||||
XCC_GENERAL_INCLUDE = u"xcc_general_include.h"
|
||||
|
||||
class PBXModifier (object):
|
||||
|
||||
def update_general_include(project, projectBasePath, shadowGroup):
|
||||
xcc_general_include_path = os.path.join(projectBasePath, XCODE_SUPPORT_FOLDER, XCC_GENERAL_INCLUDE)
|
||||
content = u""
|
||||
XCODE_SUPPORT_FOLDER = u".XcodeSupport"
|
||||
SLASH_REPLACEMENT = u"∕" # DIVISION SLASH Unicode U+2215
|
||||
STRING_RE = re.compile(ur"^\s*<string>(.*)</string>\s*$", re.MULTILINE)
|
||||
FRAMEWORKS_RE = re.compile(ur"^(.+/Frameworks/(?:Debug|Source)/([^/]+))/.+$")
|
||||
XCC_GENERAL_INCLUDE = u"xcc_general_include.h"
|
||||
|
||||
for path in os.listdir(os.path.join(projectBasePath, XCODE_SUPPORT_FOLDER)):
|
||||
filename = unicode(os.path.basename(path))
|
||||
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)
|
||||
|
||||
if filename.endswith(".h") and filename != XCC_GENERAL_INCLUDE:
|
||||
content += u'#include "{0}"\n'.format(filename)
|
||||
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
|
||||
|
||||
f = open(xcc_general_include_path, "w")
|
||||
f.write(content.encode("utf-8"))
|
||||
f.close()
|
||||
@property
|
||||
def frameworksGroup(self):
|
||||
if self._frameworksGroup is None:
|
||||
self._frameworksGroup = self.project.get_or_create_group(u"Frameworks", parent=self.sourceGroup)
|
||||
|
||||
if len(project.get_files_by_os_path(os.path.join(XCODE_SUPPORT_FOLDER, XCC_GENERAL_INCLUDE))) == 0:
|
||||
project.add_file(xcc_general_include_path, parent=shadowGroup)
|
||||
return self._frameworksGroup
|
||||
|
||||
def file_with_path(path, projectPath, project):
|
||||
relPath = os.path.relpath(path, projectPath)
|
||||
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 fileRef in [f for f in project.objects.values() if f.get("isa") == "PBXFileReference"]:
|
||||
filePath = path if fileRef.get("sourceTree") == "<absolute>" else relPath
|
||||
for path in os.listdir(os.path.join(self.projectRootPath, self.XCODE_SUPPORT_FOLDER)):
|
||||
filename = unicode(os.path.basename(path))
|
||||
|
||||
if fileRef.get("path") == filePath:
|
||||
return fileRef
|
||||
if filename.endswith(".h") and filename != self.XCC_GENERAL_INCLUDE:
|
||||
content += u'#include "{0}"\n'.format(filename)
|
||||
|
||||
return None
|
||||
f = open(xcc_general_include_path, "w")
|
||||
f.write(content.encode("utf-8"))
|
||||
f.close()
|
||||
|
||||
def add_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationPath, sourcePath, projectBasePath):
|
||||
# Shadow files are always project-relative
|
||||
if not file_with_path(shadowHeaderPath, projectBasePath, project):
|
||||
project.add_file(shadowHeaderPath, parent=shadowGroup, tree="SOURCE_ROOT", create_build_files=False)
|
||||
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)
|
||||
|
||||
if not file_with_path(shadowImplementationPath, projectBasePath, project):
|
||||
project.add_file(shadowImplementationPath, parent=shadowGroup, tree="SOURCE_ROOT", create_build_files=False)
|
||||
def file_with_path(self, projectSourcePath):
|
||||
relativePath = os.path.relpath(projectSourcePath, self.projectRootPath)
|
||||
|
||||
# If the file is within the project directory, the file reference will be project-relative, otherwise absolute
|
||||
if sourcePath.startswith(projectBasePath):
|
||||
tree = "SOURCE_ROOT"
|
||||
else:
|
||||
tree = "<absolute>"
|
||||
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 not file_with_path(sourcePath, projectBasePath, project):
|
||||
project.add_file(sourcePath, parent=sourceGroup, tree=tree, create_build_files=False)
|
||||
if fileRef.get("path") == filePath:
|
||||
return fileRef
|
||||
|
||||
def remove_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationPath, sourcePath, projectBasePath):
|
||||
for path in (shadowHeaderPath, shadowImplementationPath, sourcePath):
|
||||
fileRef = file_with_path(path, projectBasePath, project)
|
||||
return None
|
||||
|
||||
if fileRef:
|
||||
project.remove_file(fileRef)
|
||||
def add_file(self, projectSourcePath, shadowHeaderPath, shadowImplementationPath):
|
||||
resolvedPath = os.path.realpath(projectSourcePath)
|
||||
|
||||
def xml_converter(matchObj):
|
||||
return "<string>{0}</string>".format(matchObj.group(1).encode('ascii', 'xmlcharrefreplace'))
|
||||
# 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)
|
||||
|
||||
def convert_unicode_to_xml(path):
|
||||
"""
|
||||
mod_pbxproj writes unicode data as utf-8, but since it's an xml plist
|
||||
all non-ascii characters should be converted to xml character references.
|
||||
So we do that as a post-processing phase.
|
||||
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 xml_converter(self, matchObj):
|
||||
return "<string>{0}</string>".format(matchObj.group(1).encode("ascii", "xmlcharrefreplace"))
|
||||
|
||||
def convert_unicode_to_xml(self):
|
||||
"""
|
||||
with open(path, 'rb') as f:
|
||||
content = f.read().decode('utf-8')
|
||||
mod_pbxproj writes unicode data as utf-8, but since it's an xml plist
|
||||
all non-ascii characters should be converted to xml character references.
|
||||
So we do that as a post-processing phase.
|
||||
"""
|
||||
with open(self.pbxPath, 'rb') as f:
|
||||
content = f.read().decode('utf-8')
|
||||
|
||||
with open(path, "wb") as f:
|
||||
content = STRING_RE.sub(xml_converter, content)
|
||||
f.write(content)
|
||||
with open(self.pbxPath, "wb") as f:
|
||||
content = self.STRING_RE.sub(self.xml_converter, content)
|
||||
f.write(content)
|
||||
|
||||
def add_framework_resources(project, framework, resourcesPath):
|
||||
files = project.get_files_by_os_path(resourcesPath, tree="<absolute>")
|
||||
def add_framework_resources(self, framework, resourcesPath):
|
||||
files = self.project.get_files_by_os_path(resourcesPath, tree="<absolute>")
|
||||
|
||||
if not files:
|
||||
files = project.add_file(resourcesPath, parent=None, tree="<absolute>", create_build_files=False)
|
||||
if not files:
|
||||
files = self.project.add_file(resourcesPath, parent=None, tree="<absolute>", create_build_files=False)
|
||||
|
||||
if files:
|
||||
files[0]['name'] = framework + " Resources"
|
||||
if files:
|
||||
files[0]['name'] = framework + u" Resources"
|
||||
|
||||
def save_project(project, pbxPath):
|
||||
project.save()
|
||||
convert_unicode_to_xml(pbxPath)
|
||||
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"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 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
|
||||
|
||||
action = sys.argv[1]
|
||||
return cmp(name1.lower(), name2.lower())
|
||||
|
||||
if action in ("add", "remove"):
|
||||
projectBasePath = unicode(sys.argv[2])
|
||||
projectSourcePath = unicode(sys.argv[3])
|
||||
sourcePath = os.path.realpath(projectSourcePath)
|
||||
def compare_resource_folder_ids(self, id1, id2):
|
||||
folder1 = self.project.get_obj(id1)
|
||||
name1 = folder1.get("name", folder1.get("path"))
|
||||
|
||||
shadowBasePath = os.path.join(projectBasePath, XCODE_SUPPORT_FOLDER)
|
||||
shadowBaseName = os.path.splitext(sourcePath)[0].replace(u"/", SLASH_REPLACEMENT)
|
||||
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.sort_project()
|
||||
self.project.save()
|
||||
self.convert_unicode_to_xml()
|
||||
|
||||
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")
|
||||
projectName = os.path.basename(projectBasePath)
|
||||
pbxPath = os.path.join(projectBasePath, projectName + ".xcodeproj", "project.pbxproj")
|
||||
|
||||
project = XcodeProject.Load(pbxPath)
|
||||
|
||||
shadowGroup = project.get_or_create_group("Classes")
|
||||
sourceGroup = project.get_or_create_group("Sources")
|
||||
|
||||
if action == "add":
|
||||
fileRef = file_with_path(shadowHeaderPath, projectBasePath, project)
|
||||
fileRef = self.file_with_path(shadowHeaderPath)
|
||||
|
||||
if not fileRef:
|
||||
update_general_include(project, projectBasePath, shadowGroup)
|
||||
add_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationPath, sourcePath, projectBasePath)
|
||||
self.update_general_include()
|
||||
self.add_file(projectSourcePath, shadowHeaderPath, shadowImplementationPath)
|
||||
|
||||
match = FRAMEWORKS_RE.match(projectSourcePath)
|
||||
match = self.FRAMEWORKS_RE.match(projectSourcePath)
|
||||
|
||||
if match:
|
||||
framework = match.group(2)
|
||||
resourcesPath = os.path.realpath(os.path.join(match.group(1), "Resources"))
|
||||
resourcesPath = os.path.realpath(os.path.join(match.group(1), u"Resources"))
|
||||
|
||||
if os.path.isdir(resourcesPath):
|
||||
add_framework_resources(project, framework, resourcesPath)
|
||||
|
||||
save_project(project, pbxPath)
|
||||
self.add_framework_resources(framework, resourcesPath)
|
||||
|
||||
elif action == "remove":
|
||||
update_general_include(project, projectBasePath, shadowGroup)
|
||||
remove_file(project, shadowGroup, sourceGroup, shadowHeaderPath, shadowImplementationPath, sourcePath, projectBasePath)
|
||||
save_project(project, pbxPath)
|
||||
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()
|
||||
|
||||
@@ -1,78 +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 <Growl/Growl.h>
|
||||
|
||||
enum XCCAPIMode {
|
||||
kXCCAPIModeAuto = 0,
|
||||
kXCCAPIModeFile,
|
||||
kXCCAPIModeFolder
|
||||
};
|
||||
|
||||
extern NSString * const XCCDidPopulateProjectNotification;
|
||||
extern NSString * const XCCConversionDidStartNotification;
|
||||
extern NSString * const XCCConversionDidStopNotification;
|
||||
extern NSString * const XCCListeningDidStartNotification;
|
||||
|
||||
|
||||
@interface TNXcodeCapp : NSObject <NSTableViewDelegate, NSUserNotificationCenterDelegate, GrowlApplicationBridgeDelegate>
|
||||
|
||||
@property NSString* XcodeSupportProjectPath;
|
||||
@property NSString* currentProjectPath;
|
||||
@property NSString* currentAPIMode;
|
||||
@property BOOL supportsFileBasedListening;
|
||||
@property BOOL reactToInodeModification;
|
||||
@property BOOL isListening;
|
||||
@property BOOL supportsFileLevelAPI;
|
||||
@property BOOL isUsingFileLevelAPI;
|
||||
@property BOOL isLoadingProject;
|
||||
@property NSMutableArray* errorList;
|
||||
|
||||
@property (unsafe_unretained) IBOutlet NSTableView *errorTable;
|
||||
@property (strong) IBOutlet NSPanel *errorsPanel;
|
||||
@property (strong) IBOutlet NSArrayController *errorListController;
|
||||
|
||||
- (IBAction)openErrorsPanel:(id)sender;
|
||||
- (IBAction)clearErrors:(id)sender;
|
||||
- (IBAction)openErrorInEditor:(id)sender;
|
||||
- (IBAction)openInXcode:(id)aSender;
|
||||
- (IBAction)resetProject:(id)aSender;
|
||||
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
- (void)listenToProjectAtPath:(NSString *)path;
|
||||
- (void)updateLastEventId:(uint64_t)eventId;
|
||||
- (BOOL)pathMatchesIgnoredPaths:(NSString*)aPath;
|
||||
- (BOOL)isObjjFile:(NSString *)path;
|
||||
- (BOOL)isXibFile:(NSString *)path;
|
||||
- (BOOL)isXCCIgnoreFile:(NSString *)path;
|
||||
- (void)tidyShadowedFiles;
|
||||
- (void)handleFileModificationAtPath:(NSString*)path notify:(BOOL)shouldNotify;
|
||||
- (void)handleFileRemovalAtPath:(NSString*)path;
|
||||
- (void)updateUserDefaultsWithLastEventId;
|
||||
- (BOOL)hasErrors;
|
||||
|
||||
@end
|
||||
|
||||
@interface TNXcodeCapp (SnowLeopard)
|
||||
|
||||
- (void)updateLastModificationDate:(NSDate *)date forPath:(NSString *)path;
|
||||
- (NSDate *)lastModificationDateForPath:(NSString *)path;
|
||||
|
||||
@end
|
||||
@@ -15,7 +15,7 @@ extern NSString * const kDefaultLastEventId;
|
||||
extern NSString * const kDefaultFirstLaunch;
|
||||
extern NSString * const kDefaultFirstLaunchVersion;
|
||||
extern NSString * const kDefaultXCCAPIMode;
|
||||
extern NSString * const kDefaultXCCReactMode;
|
||||
extern NSString * const kDefaultXCCReactToInodeMod;
|
||||
extern NSString * const kDefaultXCCReopenLastProject;
|
||||
extern NSString * const kDefaultXCCAutoOpenErrorsPanelOnWarnings;
|
||||
extern NSString * const kDefaultXCCAutoOpenErrorsPanelOnErrors;
|
||||
@@ -23,5 +23,8 @@ 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
|
||||
|
||||
@@ -13,7 +13,7 @@ NSString * const kDefaultLastEventId = @"lastEventId";
|
||||
NSString * const kDefaultFirstLaunch = @"FirstLaunch";
|
||||
NSString * const kDefaultFirstLaunchVersion = @"firstLaunchVersion";
|
||||
NSString * const kDefaultXCCAPIMode = @"XCCAPIMode";
|
||||
NSString * const kDefaultXCCReactMode = @"XCCReactMode";
|
||||
NSString * const kDefaultXCCReactToInodeMod = @"XCCReactMode";
|
||||
NSString * const kDefaultXCCReopenLastProject = @"XCCReopenLastProject";
|
||||
NSString * const kDefaultXCCAutoOpenErrorsPanelOnWarnings = @"XCCAutoOpenErrorsPanelOnWarnings";
|
||||
NSString * const kDefaultXCCAutoOpenErrorsPanelOnErrors = @"XCCAutoOpenErrorsPanelOnErrors";
|
||||
@@ -21,3 +21,6 @@ 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
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 <Growl/Growl.h>
|
||||
|
||||
// FSEvent listening mode we use
|
||||
enum XCCAPIMode {
|
||||
kXCCAPIModeAuto = 0,
|
||||
kXCCAPIModeFile,
|
||||
kXCCAPIModeFolder
|
||||
};
|
||||
|
||||
// Type of output expected from runTaskWithLaunchPath:arguments:returnType:
|
||||
enum XCCTaskReturnType {
|
||||
kTaskReturnTypeNone,
|
||||
kTaskReturnTypeStdOut,
|
||||
kTaskReturnTypeStdError
|
||||
};
|
||||
typedef enum XCCTaskReturnType XCCTaskReturnType;
|
||||
|
||||
// Status codes returned by support scripts run as tasks
|
||||
enum {
|
||||
XCCStatusCodeError = 1,
|
||||
XCCStatusCodeWarning = 2
|
||||
};
|
||||
|
||||
// Notifications we send
|
||||
extern NSString * const XCCConversionDidStartNotification;
|
||||
extern NSString * const XCCConversionDidEndNotification;
|
||||
extern NSString * const XCCProjectDidFinishLoadingNotification;
|
||||
|
||||
|
||||
@interface XcodeCapp : NSObject <NSTableViewDelegate, NSUserNotificationCenterDelegate, GrowlApplicationBridgeDelegate>
|
||||
|
||||
/*
|
||||
Every time the user opens or closes a project, this is incremented. It is passed to threaded operations
|
||||
which load the project. These threaded operations generate notifications that get queued up
|
||||
on the main thread. Because the user may cancel and start a new load while notifications are
|
||||
still queued from the previous load, we compare the projectId returned by a notification
|
||||
with the current projectId. If they don't match, we let the notification drain.
|
||||
*/
|
||||
@property NSInteger projectId;
|
||||
|
||||
// Full path to .XcodeSupport
|
||||
@property NSString *supportPath;
|
||||
|
||||
// Full path to the Cappuccino project root directory
|
||||
@property NSString *projectPath;
|
||||
|
||||
// Full path to parser.j
|
||||
@property NSString *parserPath;
|
||||
|
||||
// Full path to pbxprojModifier.py
|
||||
@property NSString *pbxModifierScriptPath;
|
||||
|
||||
// Full paths to the executables we rely on: jsc, objj, nib2cib, python
|
||||
@property NSMutableDictionary *executablePaths;
|
||||
|
||||
// Whether the current OS supports file-level FSEvents (10.7+)
|
||||
@property BOOL supportsFileLevelAPI;
|
||||
|
||||
// Whether we are actually using file-level FSEvents
|
||||
@property BOOL isUsingFileLevelAPI;
|
||||
|
||||
// Whether we should process source files that have inode-only modifications
|
||||
@property BOOL reactToInodeModification;
|
||||
|
||||
// Whether we are in the process of loading a project
|
||||
@property BOOL isLoadingProject;
|
||||
|
||||
// Whether we are currently processing source files
|
||||
@property BOOL isProcessing;
|
||||
|
||||
// A mapping from full paths to project-relative paths
|
||||
@property NSMutableDictionary *projectPathsForSourcePaths;
|
||||
|
||||
// A list of errors generated from the current batch of source processing
|
||||
@property NSMutableArray *errorList;
|
||||
|
||||
// Panel, table and controller used to display errors
|
||||
@property (strong) IBOutlet NSPanel *errorsPanel;
|
||||
@property (unsafe_unretained) IBOutlet NSTableView *errorTable;
|
||||
@property (strong) IBOutlet NSArrayController *errorListController;
|
||||
|
||||
- (IBAction)openErrorsPanel:(id)sender;
|
||||
- (IBAction)clearErrors:(id)sender;
|
||||
- (IBAction)openErrorInEditor:(id)sender;
|
||||
- (IBAction)openProjectInXcode:(id)aSender;
|
||||
- (IBAction)resetProject:(id)aSender;
|
||||
|
||||
- (BOOL)executablesAreAccessible;
|
||||
- (void)stop;
|
||||
- (void)loadProjectAtPath:(NSString *)path;
|
||||
- (BOOL)pathMatchesIgnoredPaths:(NSString*)aPath;
|
||||
|
||||
- (BOOL)notificationBelongsToCurrentProject:(NSNotification *)note;
|
||||
|
||||
- (BOOL)isObjjFile:(NSString *)path;
|
||||
- (BOOL)isXibFile:(NSString *)path;
|
||||
- (BOOL)isXCCIgnoreFile:(NSString *)path;
|
||||
|
||||
- (NSString *)shadowBasePathForProjectSourcePath:(NSString *)path;
|
||||
- (BOOL)hasErrors;
|
||||
|
||||
- (void)computeIgnoredPaths;
|
||||
- (BOOL)shouldIgnoreDirectoryNamed:(NSString *)filename;
|
||||
|
||||
- (void)wantUserNotificationWithInfo:(NSDictionary *)info;
|
||||
- (NSDictionary *)runTaskWithLaunchPath:(NSString *)launchPath arguments:(NSArray *)arguments returnType:(XCCTaskReturnType)returnType;
|
||||
|
||||
@end
|
||||
|
||||
@interface XcodeCapp (SnowLeopard)
|
||||
|
||||
- (void)updateLastModificationDate:(NSDate *)date forPath:(NSString *)path;
|
||||
- (NSDate *)lastModificationDateForPath:(NSString *)path;
|
||||
|
||||
@end
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* This file is a part of program XcodeCapp
|
||||
* Copyright (C) 2011 Aparajita Fishman (<aparajita@aparajitaworld.com>)
|
||||
*
|
||||
* 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_macros_h
|
||||
#define xcodecapp_cocoa_macros_h
|
||||
|
||||
#if DEBUG
|
||||
# define DLog(fmt, ...) NSLog((fmt), ##__VA_ARGS__)
|
||||
#else
|
||||
# define DLog(...)
|
||||
#endif
|
||||
|
||||
#endif
|
||||