//
// ObjectGraph.m
// ObjectGraph
//
// Created by vampirewalk on 2015/1/13.
// Copyright (c) 2015 mocacube. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#import "ObjectGraph.h"
#import "VWKShellHandler.h"
#import "VWKWorkspaceManager.h"
#import "VWKProject.h"
static ObjectGraph *sharedPlugin;
static NSString *BIN_PATH = @"/bin";
static NSString *USER_BIN_PATH = @"/usr/bin";
static NSString *USER_LOCAL_BIN_PATH = @"/usr/local/bin";
static NSString *PYTHON_EXECUTABLE = @"python";
static NSString *GRAPHVIZ_EXECUTABLE = @"dot";
static NSString *MOVE_EXECUTABLE = @"mv";
static NSString *OPEN_EXECUTABLE = @"open";
typedef void(^TaskBlock)(NSTask *t, NSString *standardOutputString, NSString *standardErrorString);
@interface ObjectGraph()
@property (nonatomic, strong, readwrite) NSBundle *bundle;
@property (nonatomic, strong) NSMenuItem *drawObjectGraphItem;
@property (nonatomic, strong) NSMenuItem *pathItem;
@property (nonatomic, copy) NSString *sourceCodePath;
@property (nonatomic, copy) TaskBlock getGraphvizExecutablePathBlock;
@property (nonatomic, copy) TaskBlock convertToPDFBlock;
@property (nonatomic, copy) TaskBlock movePDFFileBlock;
@property (nonatomic, copy) TaskBlock moveDOTFileBlock;
@property (nonatomic, copy) TaskBlock openBlock;
@end
@implementation ObjectGraph
+ (void)pluginDidLoad:(NSBundle *)plugin
{
static dispatch_once_t onceToken;
NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"];
if ([currentApplicationName isEqual:@"Xcode"]) {
dispatch_once(&onceToken, ^{
sharedPlugin = [[self alloc] initWithBundle:plugin];
});
}
}
+ (instancetype)sharedPlugin
{
return sharedPlugin;
}
+ (NSBundle *)pluginBundle
{
return [NSBundle bundleForClass:self];
}
- (id)initWithBundle:(NSBundle *)plugin
{
if (self = [super init]) {
// reference to plugin's bundle, for resource access
self.bundle = plugin;
[self setDefaultSourceCodePathPath];
[self setupGetGraphvizExecutablePathBlock];
[self setupConvertToPDFBlock];
[self setupMovePDFFileBlock];
[self setupMoveDOTFileBlock];
[self setupOpenBlock];
// Create menu items, initialize UI, etc.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self addMenuItems];
}];
}
return self;
}
- (void)setDefaultSourceCodePathPath
{
VWKProject *project = [VWKProject projectForKeyWindow];
self.sourceCodePath = project.directoryPath;
}
#pragma mark - Menu
- (void)addMenuItems
{
NSMenuItem *topMenuItem = [[NSApp mainMenu] itemWithTitle:@"Product"];
if (topMenuItem) {
NSMenuItem *objectGraphMenu = [[NSMenuItem alloc] initWithTitle:@"ObjectGraph" action:nil keyEquivalent:@""];
objectGraphMenu.submenu = [[NSMenu alloc] initWithTitle:@"ObjectGraph"];
self.drawObjectGraphItem = [[NSMenuItem alloc] initWithTitle:@"Draw Object Graph" action:@selector(drawObjectGraph) keyEquivalent:@""];
[self.drawObjectGraphItem setTarget:self];
self.pathItem = [[NSMenuItem alloc] initWithTitle:@"Set Source Code PATH..." action:@selector(selectPath) keyEquivalent:@""];
[self.pathItem setTarget:self];
[[objectGraphMenu submenu] addItem:self.drawObjectGraphItem];
[[objectGraphMenu submenu] addItem:self.pathItem];
[[topMenuItem submenu] insertItem:objectGraphMenu atIndex:[topMenuItem.submenu indexOfItemWithTitle:@"Build For"]];
}
}
#pragma mark - Menu Actions
// Sample Action, for menu item:
- (void)drawObjectGraph
{
if([self isValidSourceCodePath])
{
self.sourceCodePath = self.projectPath;
}
NSString *dotFileScriptPath = [[ObjectGraph pluginBundle] pathForResource:@"objc_dep" ofType:@"py"];
if (dotFileScriptPath.length) {
[VWKShellHandler runShellCommand:[USER_BIN_PATH stringByAppendingPathComponent:PYTHON_EXECUTABLE]
withArgs:@[dotFileScriptPath, _sourceCodePath, @"-o", self.dotFileName]
directory:_sourceCodePath
completion:self.getGraphvizExecutablePathBlock];
}
}
- (void)selectPath
{
NSOpenPanel *oPanel = [NSOpenPanel openPanel];
[oPanel setCanChooseDirectories:YES];
[oPanel setCanChooseFiles:NO];
if ([oPanel runModal] == NSModalResponseOK) {
NSString *projPath = [[[oPanel URLs] objectAtIndex:0] path];
self.sourceCodePath = projPath;
}
}
#pragma mark - Private Method
- (NSString *)dotFileName
{
VWKProject *project = [VWKProject projectForKeyWindow];
NSString *dotFileName = [project.projectName stringByAppendingPathExtension:@"dot"];
return dotFileName;
}
- (NSString *)pdfFileName
{
VWKProject *project = [VWKProject projectForKeyWindow];
NSString *pdfFileName = [project.projectName stringByAppendingPathExtension:@"pdf"];
return pdfFileName;
}
- (NSString *)projectPath
{
VWKProject *project = [VWKProject projectForKeyWindow];
NSString *projectPath = project.directoryPath;
return projectPath;
}
- (BOOL)isValidSourceCodePath
{
NSFileManager *manager = [NSFileManager defaultManager];
return _sourceCodePath == nil || ![manager fileExistsAtPath:_sourceCodePath];
}
- (void)setupGetGraphvizExecutablePathBlock
{
NSDictionary *environmentDict = [[NSProcessInfo processInfo] environment];
NSString *shellString = [environmentDict objectForKey:@"SHELL"];
NSArray *args = @[@"-l",
@"-c",
@"which dot", //Assuming git is the launch path you want to run
];
__weak __typeof(&*self)weakSelf = self;
self.getGraphvizExecutablePathBlock = ^(NSTask *t,
NSString *standardOutputString,
NSString *standardErrorString)
{
__strong __typeof(&*weakSelf)strongSelf = weakSelf;
[VWKShellHandler runShellCommand:shellString
withArgs:args
directory:strongSelf.sourceCodePath
completion:strongSelf.convertToPDFBlock];
};
}
- (void)setupConvertToPDFBlock
{
__weak __typeof(&*self)weakSelf = self;
self.convertToPDFBlock = ^(NSTask *t,
NSString *standardOutputString,
NSString *standardErrorString){
__strong __typeof(&*weakSelf)strongSelf = weakSelf;
if (standardOutputString.length > 0) {
NSString *GRAPHVIZ_EXECUTABLE_PATH = [standardOutputString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[VWKShellHandler runShellCommand:GRAPHVIZ_EXECUTABLE_PATH
withArgs:@[@"-Tpdf", strongSelf.dotFileName, @"-o", strongSelf.pdfFileName]
directory:strongSelf.sourceCodePath
completion:strongSelf.movePDFFileBlock];
}
};
}
- (void)setupMovePDFFileBlock
{
__weak __typeof(&*self)weakSelf = self;
self.movePDFFileBlock = ^(NSTask *t,
NSString *standardOutputString,
NSString *standardErrorString){
__strong __typeof(&*weakSelf)strongSelf = weakSelf;
[VWKShellHandler runShellCommand:[BIN_PATH stringByAppendingPathComponent:MOVE_EXECUTABLE]
withArgs:@[strongSelf.pdfFileName, strongSelf.projectPath]
directory:strongSelf.sourceCodePath
completion:strongSelf.moveDOTFileBlock];
};
}
- (void)setupMoveDOTFileBlock
{
__weak __typeof(&*self)weakSelf = self;
self.moveDOTFileBlock = ^(NSTask *t,
NSString *standardOutputString,
NSString *standardErrorString){
__strong __typeof(&*weakSelf)strongSelf = weakSelf;
[VWKShellHandler runShellCommand:[BIN_PATH stringByAppendingPathComponent:MOVE_EXECUTABLE]
withArgs:@[strongSelf.dotFileName, strongSelf.projectPath]
directory:strongSelf.sourceCodePath
completion:strongSelf.openBlock];
};
}
- (void)setupOpenBlock
{
__weak __typeof(&*self)weakSelf = self;
self.openBlock = ^(NSTask *t,
NSString *standardOutputString,
NSString *standardErrorString){
__strong __typeof(&*weakSelf)strongSelf = weakSelf;
[VWKShellHandler runShellCommand:[USER_BIN_PATH stringByAppendingPathComponent:OPEN_EXECUTABLE]
withArgs:@[strongSelf.pdfFileName]
directory:strongSelf.projectPath
completion:nil];
};
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end