├── ScreenShot.png ├── ObjectGraph.pdf ├── ObjectGraph.png ├── .travis.yml ├── ObjectGraph ├── ObjectGraph.png ├── ObjectGraph-Prefix.pch ├── VWKDocumentationManager.h ├── ObjectGraph.h ├── VWKWorkspaceManager.h ├── VWKXCodeConsole.h ├── VWKShellHandler.h ├── VWKDocumentationManager.m ├── VWKRunOperation.h ├── Info.plist ├── VWKProject.h ├── VWKShellHandler.m ├── VWKWorkspaceManager.m ├── VWKXCodeConsole.m ├── VWKProject.m ├── objc_dep │ ├── objc_dep.py │ └── README.md ├── ObjectGraph.m └── VWKRunOperation.m ├── ObjectGraph-Xcode.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ └── ObjectGraph.xcscheme └── project.pbxproj ├── ObjectGraph.dot ├── LICENSE ├── README.md └── .gitignore /ScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirewalk/ObjectGraph-Xcode/HEAD/ScreenShot.png -------------------------------------------------------------------------------- /ObjectGraph.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirewalk/ObjectGraph-Xcode/HEAD/ObjectGraph.pdf -------------------------------------------------------------------------------- /ObjectGraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirewalk/ObjectGraph-Xcode/HEAD/ObjectGraph.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | xcode_project: ObjectGraph-Xcode.xcodeproj 3 | xcode_scheme: ObjectGraph 4 | -------------------------------------------------------------------------------- /ObjectGraph/ObjectGraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vampirewalk/ObjectGraph-Xcode/HEAD/ObjectGraph/ObjectGraph.png -------------------------------------------------------------------------------- /ObjectGraph/ObjectGraph-Prefix.pch: -------------------------------------------------------------------------------- 1 | 2 | #ifdef __OBJC__ 3 | #import 4 | 5 | #import "VWKXCodeConsole.h" 6 | #endif 7 | -------------------------------------------------------------------------------- /ObjectGraph-Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ObjectGraph.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | node [shape=box]; 3 | "VWKShellHandler" -> "VWKRunOperation"; 4 | "VWKRunOperation" -> {}; 5 | "VWKDocumentationManager" -> {}; 6 | "ObjectGraph" -> "VWKProject"; 7 | "ObjectGraph" -> "VWKWorkspaceManager"; 8 | "ObjectGraph" -> "VWKShellHandler"; 9 | "VWKXCodeConsole" -> {}; 10 | 11 | "ObjectGraph-Prefix" [color=red]; 12 | "ObjectGraph-Prefix" -> "VWKXCodeConsole" [color=red]; 13 | 14 | edge [color=blue, dir=both]; 15 | "VWKProject" -> "VWKWorkspaceManager"; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /ObjectGraph/VWKDocumentationManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCPDocumentationManager.h 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | #import 25 | 26 | @interface VWKDocumentationManager : NSObject 27 | 28 | + (NSString *)docsetInstallPath; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /ObjectGraph/ObjectGraph.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObjectGraph.h 3 | // ObjectGraph 4 | // 5 | // Created by vampirewalk on 2015/1/13. 6 | // Copyright (c) 2015年 mocacube. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | // IN THE SOFTWARE. 25 | 26 | #import 27 | 28 | @interface ObjectGraph : NSObject 29 | 30 | + (instancetype)sharedPlugin; 31 | 32 | @property (nonatomic, strong, readonly) NSBundle* bundle; 33 | @end -------------------------------------------------------------------------------- /ObjectGraph/VWKWorkspaceManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCPWorkspaceManager.h 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | @interface VWKWorkspaceManager : NSObject 25 | 26 | + (id)workspaceForKeyWindow; 27 | 28 | + (NSArray *)installedPodNamesInCurrentWorkspace; 29 | 30 | + (NSString *)currentWorkspaceDirectoryPath; 31 | + (NSString *)directoryPathForWorkspace:(id)workspace; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Kevin Shen 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of ObjectGraph-Xcode nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /ObjectGraph/VWKXCodeConsole.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCPXCodeConsole.h 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | #import 25 | 26 | @interface VWKXCodeConsole : NSObject 27 | 28 | + (instancetype)consoleForKeyWindow; 29 | 30 | - (void)appendText:(NSString *)text; 31 | - (void)appendText:(NSString *)text color:(NSColor *)color; 32 | 33 | - (void)log:(id)obj; 34 | - (void)error:(id)obj; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /ObjectGraph/VWKShellHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCPShellHandler.h 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | #import 25 | 26 | @interface VWKShellHandler : NSObject 27 | 28 | + (void)runShellCommand:(NSString *)command withArgs:(NSArray *)args directory:(NSString *)directory completion:(void (^)(NSTask *t, NSString *standardOutputString, NSString *standardErrorString))completion; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /ObjectGraph/VWKDocumentationManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCPDocumentationManager.m 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | #import "VWKDocumentationManager.h" 25 | 26 | static NSString *RELATIVE_DOCSET_PATH = @"/Library/Developer/Shared/Documentation/DocSets/"; 27 | 28 | @implementation VWKDocumentationManager 29 | 30 | 31 | + (NSString *)docsetInstallPath 32 | { 33 | return [NSString pathWithComponents:@[NSHomeDirectory(), RELATIVE_DOCSET_PATH]]; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /ObjectGraph/VWKRunOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCPRunOperation.h 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | 25 | @interface VWKRunOperation : NSOperation 26 | 27 | @property (strong, nonatomic) NSMutableString *standardOutputString; 28 | @property (strong, nonatomic) NSMutableString *standardErrorString; 29 | 30 | - (id)initWithTask:(NSTask *)task; 31 | - (instancetype)initWithTask:(NSTask *)task standardOutputString:(NSMutableString *) aStandardOutputString standardErrorString:(NSMutableString*) aStandardErrorString; 32 | 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ObjectGraph 2 | 3 | ![](https://travis-ci.org/vampirewalk/ObjectGraph-Xcode.svg?branch=master) 4 | [![Twitter: @vampirewalk666](https://img.shields.io/badge/contact-%40vampirewalk-blue.svg)](https://twitter.com/vampirewalk666) 5 | 6 | ObjectGraph can show oriented graph of dependencies between classes in your project. 7 | This plugin is based on [objc_dep](https://github.com/nst/objc_dep) and [Graphviz](http://www.graphviz.org/). 8 | 9 | ![Screenshot](https://raw.githubusercontent.com/vampirewalk/ObjectGraph-Xcode/master/ScreenShot.png) 10 | 11 | ![Example](https://raw.githubusercontent.com/vampirewalk/ObjectGraph-Xcode/master/ObjectGraph.png) 12 | 13 | - Black arrows: imports 14 | - Red arrows: .pch imports 15 | - Blue arrows: two ways imports 16 | 17 | ## Usage 18 | The default path of source code parsing is project root path, you can set new path by clicking "Set Source Path Path...". 19 | For example, if you just want to parse your product class, not test class or external library class in Pods directory, you can set path to directory which contains your product class and execute "Draw Object Graph" again. 20 | 21 | ## Install 22 | First step, install Graphviz via Homebrew 23 | ``` 24 | brew install graphviz 25 | ``` 26 | or MacPorts 27 | ``` 28 | sudo port install graphviz 29 | ``` 30 | Second, install ObjectGraph-Xcode via [Alcatraz](http://alcatraz.io/) 31 | or 32 | Clone and build the project, then restart Xcode. 33 | 34 | ## Uninstall 35 | Uninstall it via [Alcatraz](http://alcatraz.io/) 36 | or 37 | ``` 38 | Run rm -r ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins/ObjectGraph.xcplugin/ 39 | ``` 40 | 41 | ## Thanks 42 | Thanks to kattrali, I get inspiration and import some code from [cocoapods-xcode-plugin](https://github.com/kattrali/cocoapods-xcode-plugin). 43 | 44 | -------------------------------------------------------------------------------- /ObjectGraph/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | net.vampirewalk.$(PRODUCT_NAME:rfc1034identifier) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | DVTPlugInCompatibilityUUIDs 26 | 27 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 28 | E969541F-E6F9-4D25-8158-72DC3545A6C6 29 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 30 | A16FF353-8441-459E-A50C-B071F53F51B7 31 | C4A681B0-4A26-480E-93EC-1218098B9AA0 32 | AD68E85B-441B-4301-B564-A45E4919A6AD 33 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 34 | CC0D0F4F-05B3-431A-8F33-F84AFCB2C651 35 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 36 | 7265231C-39B4-402C-89E1-16167C4CC990 37 | F41BD31E-2683-44B8-AE7F-5F09E919790E 38 | ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C 39 | DFFB3951-EB0A-4C09-9DAC-5F2D28CC839C 40 | 41 | LSMinimumSystemVersion 42 | $(MACOSX_DEPLOYMENT_TARGET) 43 | NSPrincipalClass 44 | ObjectGraph 45 | XC4Compatible 46 | 47 | XCPluginHasUI 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ObjectGraph/VWKProject.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCPWorkspace.h 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | @interface VWKProject : NSObject 25 | 26 | @property (nonatomic, strong) NSString *directoryPath; 27 | 28 | @property (nonatomic, strong) NSString *podspecPath; 29 | @property (nonatomic, strong) NSString *podfilePath; 30 | 31 | @property (nonatomic, readonly) NSString *workspacePath; 32 | 33 | @property (nonatomic, strong) NSString *projectName; 34 | 35 | @property (nonatomic, strong) NSDictionary *infoDictionary; 36 | 37 | + (instancetype)projectForKeyWindow; 38 | 39 | - (id)initWithName:(NSString *)name 40 | path:(NSString *)path; 41 | 42 | - (void)createPodspecFromTemplate:(NSString *)_template; 43 | 44 | - (BOOL)hasPodfile; 45 | - (BOOL)hasPodspecFile; 46 | 47 | - (BOOL)containsFileWithName:(NSString *)fileName; 48 | 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /ObjectGraph/VWKShellHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCPShellHandler.m 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | #import "VWKShellHandler.h" 25 | 26 | #import 27 | 28 | #import "VWKRunOperation.h" 29 | 30 | static NSOperationQueue *operationQueue; 31 | 32 | @implementation VWKShellHandler 33 | 34 | + (void)runShellCommand:(NSString *)command withArgs:(NSArray *)args directory:(NSString *)directory completion:(void (^)(NSTask *t, NSString *standardOutputString, NSString *standardErrorString))completion 35 | { 36 | if (operationQueue == nil) { 37 | operationQueue = [NSOperationQueue new]; 38 | } 39 | 40 | NSTask *task = [NSTask new]; 41 | 42 | NSMutableDictionary * environment = [[[NSProcessInfo processInfo] environment] mutableCopy]; 43 | environment[@"LC_ALL"]=@"en_US.UTF-8"; 44 | [task setEnvironment:environment]; 45 | 46 | task.currentDirectoryPath = directory; 47 | task.launchPath = command; 48 | task.arguments = args; 49 | 50 | NSMutableString *standardOutputString = [NSMutableString string]; 51 | NSMutableString *standardErrorString = [NSMutableString string]; 52 | 53 | VWKRunOperation *operation = [[VWKRunOperation alloc] initWithTask:task standardOutputString:standardOutputString standardErrorString:standardErrorString]; 54 | operation.completionBlock = ^{ 55 | if (completion) { 56 | completion(task, standardOutputString, standardErrorString); 57 | } 58 | }; 59 | [operationQueue addOperation:operation]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /ObjectGraph/VWKWorkspaceManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCPWorkspaceManager.m 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | #import "VWKWorkspaceManager.h" 25 | 26 | #import "VWKProject.h" 27 | 28 | static NSString *PODFILE = @"Podfile"; 29 | 30 | @implementation VWKWorkspaceManager 31 | 32 | + (NSArray *)installedPodNamesInCurrentWorkspace 33 | { 34 | NSMutableArray *names = [NSMutableArray new]; 35 | id workspace = [self workspaceForKeyWindow]; 36 | 37 | id contextManager = [workspace valueForKey:@"_runContextManager"]; 38 | for (id scheme in[contextManager valueForKey:@"runContexts"]) { 39 | NSString *schemeName = [scheme valueForKey:@"name"]; 40 | if ([schemeName hasPrefix:@"Pods-"]) { 41 | [names addObject:[schemeName stringByReplacingOccurrencesOfString:@"Pods-" withString:@""]]; 42 | } 43 | } 44 | return names; 45 | } 46 | 47 | + (NSString *)currentWorkspaceDirectoryPath 48 | { 49 | return [self directoryPathForWorkspace:[self workspaceForKeyWindow]]; 50 | } 51 | 52 | + (NSString *)directoryPathForWorkspace:(id)workspace 53 | { 54 | NSString *workspacePath = [[workspace valueForKey:@"representingFilePath"] valueForKey:@"_pathString"]; 55 | return [workspacePath stringByDeletingLastPathComponent]; 56 | } 57 | 58 | #pragma mark - Private 59 | 60 | + (id)workspaceForKeyWindow 61 | { 62 | return [self workspaceForWindow:[NSApp keyWindow]]; 63 | } 64 | 65 | + (id)workspaceForWindow:(NSWindow *)window 66 | { 67 | NSArray *workspaceWindowControllers = [NSClassFromString(@"IDEWorkspaceWindowController") valueForKey:@"workspaceWindowControllers"]; 68 | 69 | for (id controller in workspaceWindowControllers) { 70 | if ([[controller valueForKey:@"window"] isEqual:window]) { 71 | return [controller valueForKey:@"_workspace"]; 72 | } 73 | } 74 | return nil; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /ObjectGraph-Xcode.xcodeproj/xcshareddata/xcschemes/ObjectGraph.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 46 | 49 | 50 | 51 | 57 | 58 | 59 | 60 | 61 | 62 | 68 | 69 | 75 | 76 | 77 | 78 | 80 | 81 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/xcode,swift,osx,carthage,objective-c 2 | 3 | ### Xcode ### 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | 23 | ## Other 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | 28 | 29 | ### Swift ### 30 | # Xcode 31 | # 32 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 33 | 34 | ## Build generated 35 | build/ 36 | DerivedData 37 | 38 | ## Various settings 39 | *.pbxuser 40 | !default.pbxuser 41 | *.mode1v3 42 | !default.mode1v3 43 | *.mode2v3 44 | !default.mode2v3 45 | *.perspectivev3 46 | !default.perspectivev3 47 | xcuserdata 48 | 49 | ## Other 50 | *.xccheckout 51 | *.moved-aside 52 | *.xcuserstate 53 | *.xcscmblueprint 54 | 55 | ## Obj-C/Swift specific 56 | *.hmap 57 | *.ipa 58 | 59 | # CocoaPods 60 | # 61 | # We recommend against adding the Pods directory to your .gitignore. However 62 | # you should judge for yourself, the pros and cons are mentioned at: 63 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 64 | # 65 | # Pods/ 66 | 67 | # Carthage 68 | # 69 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 70 | # Carthage/Checkouts 71 | 72 | Carthage/Build 73 | 74 | 75 | ### OSX ### 76 | .DS_Store 77 | .AppleDouble 78 | .LSOverride 79 | 80 | # Icon must end with two \r 81 | Icon 82 | 83 | 84 | # Thumbnails 85 | ._* 86 | 87 | # Files that might appear in the root of a volume 88 | .DocumentRevisions-V100 89 | .fseventsd 90 | .Spotlight-V100 91 | .TemporaryItems 92 | .Trashes 93 | .VolumeIcon.icns 94 | 95 | # Directories potentially created on remote AFP share 96 | .AppleDB 97 | .AppleDesktop 98 | Network Trash Folder 99 | Temporary Items 100 | .apdisk 101 | 102 | 103 | ### Carthage ### 104 | # Carthage - A simple, decentralized dependency manager for Cocoa 105 | Carthage.checkout 106 | Carthage.build 107 | 108 | ### Objective-C ### 109 | # Xcode 110 | # 111 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 112 | 113 | ## Build generated 114 | build/ 115 | DerivedData 116 | 117 | ## Various settings 118 | *.pbxuser 119 | !default.pbxuser 120 | *.mode1v3 121 | !default.mode1v3 122 | *.mode2v3 123 | !default.mode2v3 124 | *.perspectivev3 125 | !default.perspectivev3 126 | xcuserdata 127 | 128 | ## Other 129 | *.xccheckout 130 | *.moved-aside 131 | *.xcuserstate 132 | *.xcscmblueprint 133 | 134 | ## Obj-C/Swift specific 135 | *.hmap 136 | *.ipa 137 | 138 | # CocoaPods 139 | # 140 | # We recommend against adding the Pods directory to your .gitignore. However 141 | # you should judge for yourself, the pros and cons are mentioned at: 142 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 143 | # 144 | #Pods/ 145 | 146 | # Carthage 147 | # 148 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 149 | # Carthage/Checkouts 150 | 151 | Carthage/Build 152 | 153 | ### Objective-C Patch ### 154 | *.xcscmblueprint 155 | -------------------------------------------------------------------------------- /ObjectGraph/VWKXCodeConsole.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCPXCodeConsole.m 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | #import "VWKXCodeConsole.h" 25 | 26 | static NSMutableDictionary *sharedInstances; 27 | 28 | @interface VWKXCodeConsole () 29 | 30 | @property (retain, nonatomic) NSTextView *console; 31 | @property (strong, nonatomic) NSString *windowIdentifier; 32 | 33 | @end 34 | 35 | 36 | @implementation VWKXCodeConsole 37 | 38 | - (id)initWithIdentifier:(NSString *)identifier 39 | { 40 | if (self = [super init]) { 41 | _windowIdentifier = identifier; 42 | } 43 | 44 | return self; 45 | } 46 | 47 | - (NSTextView *)console 48 | { 49 | if (!_console) { 50 | _console = [self findConsoleAndActivate]; 51 | } 52 | return _console; 53 | } 54 | 55 | - (void)log:(id)obj 56 | { 57 | [self appendText:[NSString stringWithFormat:@"%@\n", obj]]; 58 | } 59 | 60 | - (void)error:(id)obj 61 | { 62 | [self appendText:[NSString stringWithFormat:@"%@\n", obj] 63 | color:[NSColor redColor]]; 64 | } 65 | 66 | - (void)appendText:(NSString *)text 67 | { 68 | [self appendText:text color:nil]; 69 | } 70 | 71 | - (NSWindow *)window 72 | { 73 | for (NSWindow * window in [NSApp windows]) { 74 | if ([[window description] isEqualToString:self.windowIdentifier]) { 75 | return window; 76 | } 77 | } 78 | return nil; 79 | } 80 | 81 | - (void)appendText:(NSString *)text color:(NSColor *)color 82 | { 83 | if (text.length == 0) return; 84 | 85 | if (!color) 86 | color = self.console.textColor; 87 | 88 | NSMutableDictionary *attributes = [@{ NSForegroundColorAttributeName: color } mutableCopy]; 89 | NSFont *font = [NSFont fontWithName:@"Menlo Regular" size:11]; 90 | if (font) { 91 | attributes[NSFontAttributeName] = font; 92 | } 93 | NSAttributedString *as = [[NSAttributedString alloc] initWithString:text attributes:attributes]; 94 | NSRange theEnd = NSMakeRange(self.console.string.length, 0); 95 | theEnd.location += as.string.length; 96 | if (NSMaxY(self.console.visibleRect) == NSMaxY(self.console.bounds)) { 97 | [self.console.textStorage appendAttributedString:as]; 98 | [self.console scrollRangeToVisible:theEnd]; 99 | } else { 100 | [self.console.textStorage appendAttributedString:as]; 101 | } 102 | } 103 | 104 | #pragma mark - Class Methods 105 | 106 | + (instancetype)consoleForKeyWindow 107 | { 108 | return [self consoleForWindow:[NSApp keyWindow]]; 109 | } 110 | 111 | + (instancetype)consoleForWindow:(NSWindow *)window 112 | { 113 | if (window == nil) return nil; 114 | 115 | NSString * key = [window description]; 116 | 117 | if (!sharedInstances) 118 | sharedInstances = [[NSMutableDictionary alloc] init]; 119 | 120 | if (!sharedInstances[key]) { 121 | VWKXCodeConsole *console = [[VWKXCodeConsole alloc] initWithIdentifier:key]; 122 | [sharedInstances setObject:console forKey:key]; 123 | } 124 | 125 | return sharedInstances[key]; 126 | } 127 | 128 | #pragma mark - Console Detection 129 | 130 | 131 | + (NSView *)findConsoleViewInView:(NSView *)view 132 | { 133 | Class consoleClass = NSClassFromString(@"IDEConsoleTextView"); 134 | return [self findViewOfKind:consoleClass inView:view]; 135 | } 136 | 137 | + (NSView *)findViewOfKind:(Class)kind 138 | inView:(NSView *)view 139 | { 140 | if ([view isKindOfClass:kind]) { 141 | return view; 142 | } 143 | 144 | for (NSView *v in view.subviews) { 145 | NSView *result = [self findViewOfKind:kind 146 | inView:v]; 147 | if (result) { 148 | return result; 149 | } 150 | } 151 | return nil; 152 | } 153 | 154 | - (NSTextView *)findConsoleAndActivate 155 | { 156 | NSTextView *console = (NSTextView *)[[self class] findConsoleViewInView:self.window.contentView]; 157 | if (console 158 | && [self.window isKindOfClass:NSClassFromString(@"IDEWorkspaceWindow")] 159 | && [self.window.windowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) { 160 | id editorArea = [self.window.windowController valueForKey:@"editorArea"]; 161 | [editorArea performSelector:@selector(activateConsole:) withObject:self]; 162 | } 163 | 164 | [console.textStorage deleteCharactersInRange:NSMakeRange(0, console.textStorage.length)]; 165 | 166 | return console; 167 | } 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /ObjectGraph/VWKProject.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCPWorkspace.m 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | #import 25 | 26 | #import "VWKProject.h" 27 | 28 | #import "VWKWorkspaceManager.h" 29 | 30 | @implementation VWKProject 31 | 32 | + (instancetype)projectForKeyWindow 33 | { 34 | id workspace = [VWKWorkspaceManager workspaceForKeyWindow]; 35 | 36 | id contextManager = [workspace valueForKey:@"_runContextManager"]; 37 | for (id scheme in[contextManager valueForKey:@"runContexts"]) { 38 | NSString *schemeName = [scheme valueForKey:@"name"]; 39 | if (![schemeName hasPrefix:@"Pods-"]) { 40 | NSString *path = [VWKWorkspaceManager directoryPathForWorkspace:workspace]; 41 | return [[VWKProject alloc] initWithName:schemeName path:path]; 42 | } 43 | } 44 | 45 | return nil; 46 | } 47 | 48 | - (id)initWithName:(NSString *)name 49 | path:(NSString *)path 50 | { 51 | if (self = [self init]) { 52 | _projectName = name; 53 | _podspecPath = [path stringByAppendingPathComponent:[name stringByAppendingString:@".podspec"]]; 54 | _directoryPath = path; 55 | 56 | NSString *infoPath = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"%@/%@-Info.plist", _projectName, _projectName]]; 57 | 58 | _infoDictionary = [NSDictionary dictionaryWithContentsOfFile:infoPath]; 59 | _podfilePath = [path stringByAppendingPathComponent:@"Podfile"]; 60 | } 61 | 62 | return self; 63 | } 64 | 65 | - (BOOL)hasPodspecFile 66 | { 67 | return [[NSFileManager defaultManager] fileExistsAtPath:self.podspecPath]; 68 | } 69 | 70 | - (BOOL)hasPodfile 71 | { 72 | return [[NSFileManager defaultManager] fileExistsAtPath:self.podfilePath]; 73 | } 74 | 75 | - (void)createPodspecFromTemplate:(NSString *)_template 76 | { 77 | NSMutableString *podspecFile = _template.mutableCopy; 78 | NSRange range; range.location = 0; 79 | 80 | range.length = podspecFile.length; 81 | [podspecFile replaceOccurrencesOfString:@"" 82 | withString:self.projectName 83 | options:NSLiteralSearch 84 | range:range]; 85 | 86 | NSString *version = self.infoDictionary[@"CFBundleShortVersionString"]; 87 | if (version) { 88 | range.length = podspecFile.length; 89 | [podspecFile replaceOccurrencesOfString:@"" 90 | withString:version 91 | options:NSLiteralSearch 92 | range:range]; 93 | } 94 | 95 | range.length = podspecFile.length; 96 | [podspecFile replaceOccurrencesOfString:@"'<" 97 | withString:@"'<#" 98 | options:NSLiteralSearch 99 | range:range]; 100 | 101 | range.length = podspecFile.length; 102 | [podspecFile replaceOccurrencesOfString:@">'" 103 | withString:@"#>'" 104 | options:NSLiteralSearch 105 | range:range]; 106 | 107 | // Reading dependencies 108 | NSString *podfileContent = [NSString stringWithContentsOfFile:self.podfilePath encoding:NSUTF8StringEncoding error:nil]; 109 | NSArray *fileLines = [podfileContent componentsSeparatedByString:@"\n"]; 110 | 111 | for (NSString *tmp in fileLines) { 112 | NSString *line = [tmp stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 113 | 114 | if ([line rangeOfString:@"pod "].location == 0) { 115 | [podspecFile appendFormat:@"\n s.dependencies =\t%@", line]; 116 | } 117 | } 118 | 119 | [podspecFile appendString:@"\n\nend"]; 120 | 121 | // Write Podspec File 122 | [[NSFileManager defaultManager] createFileAtPath:self.podspecPath contents:nil attributes:nil]; 123 | [podspecFile writeToFile:self.podspecPath atomically:YES encoding:NSUTF8StringEncoding error:nil]; 124 | } 125 | 126 | - (BOOL)containsFileWithName:(NSString *)fileName 127 | { 128 | NSString *filePath = [self.directoryPath stringByAppendingPathComponent:fileName]; 129 | return [[NSFileManager defaultManager] fileExistsAtPath:filePath]; 130 | } 131 | 132 | #pragma mark - Overriden getters 133 | 134 | - (NSString *)workspacePath { 135 | return [NSString stringWithFormat:@"%@/%@.xcworkspace", self.directoryPath, self.projectName]; 136 | } 137 | 138 | @end 139 | -------------------------------------------------------------------------------- /ObjectGraph/objc_dep/objc_dep.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Nicolas Seriot 4 | # 2011-01-06 -> 2011-12-16 5 | # https://github.com/nst/objc_dep/ 6 | 7 | """ 8 | Input: path of an Objective-C project 9 | 10 | Output: import dependencies Graphviz format 11 | 12 | Typical usage: $ python objc_dep.py /path/to/project [-x regex] [-i subfolder [subfolder ...]] > graph.dot 13 | 14 | The .dot file can be opened with Graphviz or OmniGraffle. 15 | 16 | - red arrows: .pch imports 17 | - blue arrows: two ways imports 18 | """ 19 | 20 | import sys 21 | import os 22 | from sets import Set 23 | import re 24 | from os.path import basename 25 | import argparse 26 | 27 | local_regex_import = re.compile("^\s*#(?:import|include)\s+\"(?P\S*)(?P\.(?:h|hpp|hh))?\"") 28 | system_regex_import = re.compile("^\s*#(?:import|include)\s+[\"<](?P\S*)(?P\.(?:h|hpp|hh))?[\">]") 29 | 30 | def gen_filenames_imported_in_file(path, regex_exclude, system, extensions): 31 | for line in open(path): 32 | results = re.search(system_regex_import, line) if system else re.search(local_regex_import, line) 33 | if results: 34 | filename = results.group('filename') 35 | extension = results.group('extension') 36 | if regex_exclude is not None and regex_exclude.search(filename + extension): 37 | continue 38 | yield (filename + extension) if extension else filename 39 | 40 | def dependencies_in_project(path, ext, exclude, ignore, system, extensions): 41 | d = {} 42 | 43 | regex_exclude = None 44 | if exclude: 45 | regex_exclude = re.compile(exclude) 46 | 47 | for root, dirs, files in os.walk(path): 48 | 49 | if ignore: 50 | for subfolder in ignore: 51 | if subfolder in dirs: 52 | dirs.remove(subfolder) 53 | 54 | objc_files = (f for f in files if f.endswith(ext)) 55 | 56 | for f in objc_files: 57 | 58 | filename = f if extensions else os.path.splitext(f)[0] 59 | if regex_exclude is not None and regex_exclude.search(filename): 60 | continue 61 | 62 | if filename not in d: 63 | d[filename] = Set() 64 | 65 | path = os.path.join(root, f) 66 | 67 | for imported_filename in gen_filenames_imported_in_file(path, regex_exclude, system, extensions): 68 | if imported_filename != filename and '+' not in imported_filename and '+' not in filename: 69 | imported_filename = imported_filename if extensions else os.path.splitext(imported_filename)[0] 70 | d[filename].add(imported_filename) 71 | 72 | return d 73 | 74 | def dependencies_in_project_with_file_extensions(path, exts, exclude, ignore, system, extensions): 75 | 76 | d = {} 77 | 78 | for ext in exts: 79 | d2 = dependencies_in_project(path, ext, exclude, ignore, system, extensions) 80 | for (k, v) in d2.iteritems(): 81 | if not k in d: 82 | d[k] = Set() 83 | d[k] = d[k].union(v) 84 | 85 | return d 86 | 87 | def two_ways_dependencies(d): 88 | 89 | two_ways = Set() 90 | 91 | # d is {'a1':[b1, b2], 'a2':[b1, b3, b4], ...} 92 | 93 | for a, l in d.iteritems(): 94 | for b in l: 95 | if b in d and a in d[b]: 96 | if (a, b) in two_ways or (b, a) in two_ways: 97 | continue 98 | if a != b: 99 | two_ways.add((a, b)) 100 | 101 | return two_ways 102 | 103 | def untraversed_files(d): 104 | 105 | dead_ends = Set() 106 | 107 | for file_a, file_a_dependencies in d.iteritems(): 108 | for file_b in file_a_dependencies: 109 | if not file_b in dead_ends and not file_b in d: 110 | dead_ends.add(file_b) 111 | 112 | return dead_ends 113 | 114 | def category_files(d): 115 | d2 = {} 116 | l = [] 117 | 118 | for k, v in d.iteritems(): 119 | if not v and '+' in k: 120 | l.append(k) 121 | else: 122 | d2[k] = v 123 | 124 | return l, d2 125 | 126 | def referenced_classes_from_dict(d): 127 | d2 = {} 128 | 129 | for k, deps in d.iteritems(): 130 | for x in deps: 131 | d2.setdefault(x, Set()) 132 | d2[x].add(k) 133 | 134 | return d2 135 | 136 | def print_frequencies_chart(d): 137 | 138 | lengths = map(lambda x:len(x), d.itervalues()) 139 | if not lengths: return 140 | max_length = max(lengths) 141 | 142 | for i in range(0, max_length+1): 143 | s = "%2d | %s\n" % (i, '*'*lengths.count(i)) 144 | sys.stderr.write(s) 145 | 146 | sys.stderr.write("\n") 147 | 148 | l = [Set() for i in range(max_length+1)] 149 | for k, v in d.iteritems(): 150 | l[len(v)].add(k) 151 | 152 | for i in range(0, max_length+1): 153 | s = "%2d | %s\n" % (i, ", ".join(sorted(list(l[i])))) 154 | sys.stderr.write(s) 155 | 156 | def dependencies_in_dot_format(path, exclude, ignore, system, extensions): 157 | 158 | d = dependencies_in_project_with_file_extensions(path, ['.h', '.hh', '.hpp', '.m', '.mm', '.c', '.cc', '.cpp'], exclude, ignore, system, extensions) 159 | 160 | two_ways_set = two_ways_dependencies(d) 161 | untraversed_set = untraversed_files(d) 162 | 163 | category_list, d = category_files(d) 164 | 165 | pch_set = dependencies_in_project(path, '.pch', exclude, ignore, system, extensions) 166 | 167 | # 168 | 169 | sys.stderr.write("# number of imports\n\n") 170 | print_frequencies_chart(d) 171 | 172 | sys.stderr.write("\n# times the class is imported\n\n") 173 | d2 = referenced_classes_from_dict(d) 174 | print_frequencies_chart(d2) 175 | 176 | # 177 | 178 | l = [] 179 | l.append("digraph G {") 180 | l.append("\tnode [shape=box];") 181 | 182 | for k, deps in d.iteritems(): 183 | if deps: 184 | deps.discard(k) 185 | 186 | if len(deps) == 0: 187 | l.append("\t\"%s\" -> {};" % (k)) 188 | 189 | for k2 in deps: 190 | if not ((k, k2) in two_ways_set or (k2, k) in two_ways_set): 191 | l.append("\t\"%s\" -> \"%s\";" % (k, k2)) 192 | 193 | l.append("\t") 194 | for (k, v) in pch_set.iteritems(): 195 | l.append("\t\"%s\" [color=red];" % k) 196 | for x in v: 197 | l.append("\t\"%s\" -> \"%s\" [color=red];" % (k, x)) 198 | 199 | l.append("\t") 200 | l.append("\tedge [color=blue, dir=both];") 201 | 202 | for (k, k2) in two_ways_set: 203 | l.append("\t\"%s\" -> \"%s\";" % (k, k2)) 204 | 205 | for k in untraversed_set: 206 | l.append("\t\"%s\" [color=gray, style=dashed, fontcolor=gray]" % k) 207 | 208 | if category_list: 209 | l.append("\t") 210 | l.append("\tedge [color=black];") 211 | l.append("\tnode [shape=plaintext];") 212 | l.append("\t\"Categories\" [label=\"%s\"];" % "\\n".join(category_list)) 213 | 214 | if ignore: 215 | l.append("\t") 216 | l.append("\tnode [shape=box, color=blue];") 217 | l.append("\t\"Ignored\" [label=\"%s\"];" % "\\n".join(ignore)) 218 | 219 | l.append("}\n") 220 | return '\n'.join(l) 221 | 222 | def main(): 223 | parser = argparse.ArgumentParser() 224 | parser.add_argument("-x", "--exclude", nargs='?', default='' ,help="regular expression of substrings to exclude from module names") 225 | parser.add_argument("-i", "--ignore", nargs='*', help="list of subfolder names to ignore") 226 | parser.add_argument("-s", "--system", action='store_true', default=False, help="include system dependencies") 227 | parser.add_argument("-e", "--extensions", action='store_true', default=False, help="print file extensions") 228 | parser.add_argument("project_path", help="path to folder hierarchy containing Objective-C files") 229 | parser.add_argument("-o", "--output", help="name of output file", default="output.dot") 230 | args= parser.parse_args() 231 | sys.stdout = open(args.output, 'w') 232 | print dependencies_in_dot_format(args.project_path, args.exclude, args.ignore, args.system, args.extensions) 233 | 234 | if __name__=='__main__': 235 | main() 236 | -------------------------------------------------------------------------------- /ObjectGraph/objc_dep/README.md: -------------------------------------------------------------------------------- 1 | - 2013-04-17 merged changes from mikkelee who [added](https://github.com/nst/objc_dep/pull/6) to use a single bidirectional arrow for two-way dependencies 2 | - 2013-02-21 merged changes from jomnius who [added](https://github.com/nst/objc_dep/pull/4) a parameter to exclude directories names by regex 3 | - 2012-05-11 merged changes from jonreid who [added](https://github.com/nst/objc_dep/pull/3) a parameter to exclude class names by regex 4 | - 2012-01-11 merged changes from jomnius who [added](https://github.com/nst/objc_dep/pull/1) support for C projects and better handling of categories 5 | - read [Object graph dependency analysis](http://samuelgoodwin.tumblr.com/post/18345393597/object-graph-dependency-analysis) by Samuel Goodwin 6 | - see also jominus blog post [Dependency Graph Tool for iOS Projects](http://jomnius.blogspot.com/2012/01/dependency-graph-tool-for-ios-projects.html) 7 | - yet another use case on vigorouscoding.com [Better get it right the first time](http://www.vigorouscoding.com/2011/12/better-get-it-right-the-first-time/) 8 | 9 | # License 10 | 11 | BSD 3-Clause License 12 | 13 | Copyright (c) 2012-2013, Nicolas Seriot All rights reserved. 14 | 15 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 16 | 17 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 18 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 19 | Neither the name of the Nicolas Seriot nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 21 | 22 | # Refactoring by graphing class dependencies 23 | 24 | ### Code design and loose coupling 25 | 26 | As developers, we all love clean code, but the fact is that most of the time we're dealing with bad code. It may be recent or legacy code, written by ourselves or by other developers. We can recognize bad code because [code](http://en.wikipedia.org/wiki/Code_smell) [smells](http://www.codinghorror.com/blog/2006/05/code-smells.html). In other words, some heuristics raise questions about code quality. Among thoses we can name dead code, which I already wrote about [here](http://seriot.ch/blog.php?article=20080728) and [here](http://seriot.ch/blog.php?article=20100301), and tight coupling. 27 | 28 | Tight coupling describes a system where many components depend on many other components. A tightly coupled code base stinks, the coupling points out that some classes assume too many responsibilities or that a responsability is spread over several classes, rather than having its own class. The opposite, loose coupling, shows a better design which promotes single-responsibility and separation of concerns. Loose coupling makes the code easier to test and maintain. 29 | 30 | In Objective-C, reducing coupling generally involves [delegates](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocProtocols.html) and [notifications](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html%23//apple_ref/doc/uid/10000043i). 31 | 32 | ### Graphing class dependencies 33 | 34 | So how do we achieve loose coupling in our own code? Well, at first, we need to get a better idea on the current coupling. Let us define class dependency: _class A depends on B if class A imports class B header_. With such a definition, we can draw a graph of dependencies between classes by considering the Objective-C `#import` directives in each class. We assume here that the files are named according to the classes they contain. 35 | 36 | I wrote [objc_dep.py](https://github.com/nst/objc_dep), a Python script which extracts imports from Objective-C source code. The output can then be displayed in [GraphViz](http://www.graphviz.org/) or [OmniGraffle](http://www.omnigroup.com/products/omnigraffle/). You can then see an oriented graph of dependencies between classes. Note that we could also compute metrics on coupling, but it's not the point here. 37 | 38 | ### Sample usage 39 | 40 | How do we get from the dependencies graph to a better design? There's no determinist algorithm and it depends on your project. Let us apply the script on [FSWalker](http://code.google.com/p/fswalker/), a small iPhone file browser I wrote a long time ago. 41 | 42 | #### 1. Generate the graph 43 | 44 | $ python objc_dep.py /path/to/FSWalker > fswalker.dot 45 | 46 | #### 2. Open it in OmniGraffle 47 | 48 | At this point, we see classes as nodes and dependencies as directed edges. 49 | 50 | 51 | 52 | #### 3. Remove categories 53 | 54 | We can safely remove Objective-C [categories](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocCategories.html) from the graph, since referencing categories from many places is not an issue from a design point of view. 55 | 56 | 57 | 58 | #### 4. Group related classes 59 | 60 | Next, we can move the vertices around, try to group classes with a common responsibility into clusters. 61 | 62 | 63 | 64 | #### 5. Study strange dependencies 65 | 66 | The graph now gives a pretty good overview of the overall code structure. The controller objects have been colored in pink, the model objects in yellow and the network part in blue. The graph allows to sport strange dependencies and question the code design. We can see at first glance that FSWalkerAppDelegate has too many dependencies. Specifically we consider: 67 | 68 | a) unreferenced classes or clusters 69 | 70 | This is probably dead code, which can be removed. 71 | 72 | Ok, there are no unreferenced classes here, although you will probably find some in bigger projects. 73 | 74 | b) two-ways references 75 | 76 | Maybe one class should not reference another directly, but reference a protocol implemented by the other class instead. 77 | 78 | We have two examples of two-ways references here, between HTTPServer and HTTPConnection, and also between RootViewController and FSWalkerAppDelegate. The former is part of [CocoaHTTPServer](http://code.google.com/p/cocoahttpserver/) and is not a design issue in our project. However, the latter is an issue. By looking at the code, we will notice that RootViewController doesn't actually use FSWalkerAppDelegate. The import can thus be safely removed. 79 | 80 | c) weird references 81 | 82 | Some of the import directives may simply be unnecessary, or reveal design issues. 83 | 84 | There is no good reason why FSWalkerAppDelegate would reference FSItem, nor InfoPanelController. Code inspection will reveal that DetailViewController and InfoPanelController should not be referenced by FSWalkerAppDelegate but RootViewController instead. So, here is the final graph. The architecture of FSWalker may still be improved, but you get the idea... 85 | 86 | 87 | 88 | ### Real world usage 89 | 90 | Here is the kind of chart you can expect with a 100 classes project. 91 | 92 | 93 | 94 | #### Using options 95 | 96 | `% python objc_dep.py /path/to/repo -x "(^Internal|secret)" -i subdir1 subdir2 > graph.dot` 97 | 98 | Will exclude files with names that begin with "Internal", or contain the word "secret". Additionally all files in folders named subdir1 and subdir2 are ignored. 99 | 100 | ### Possible improvements 101 | 102 | The Cocoa framework enforces the [MVC paradigm](http://developer.apple.com/technologies/mac/cocoa.html), which states (to be short) that model objects and graphical objects should be clearly separated by controller objects. The script could probably be improved by drawing classes which depend on Foundation and classes which depend on AppKit/UIKit with different colors. 103 | 104 | ### Conclusion 105 | 106 | I have found [objc_dep.py](https://github.com/nst/objc_dep) to be definitely useful on small projects as well as bigger ones. It helped me in getting a clear view of code base structure. Spotting strange dependencies allowed me to ask good questions which led to design simplifications. Such a tool could even be integrated into Apple development tools. 107 | 108 | Interestingly, the last chart is quite close to the mental representation I have of the code architecture. It reminds me a discussion I had 15 years ago with a friend who was a champion chess player who could play several "blind" chess games simultaneously. He explained then that he saw a blurry chessboard in his head and could move around the pieces as with a small 3D camera. Focusing on a specific piece would then raise awareness of opportunities and risks - dependencies - for this piece. As a side effect, writing this script made me realize that software engineering is pretty similar to chess in this way. 109 | -------------------------------------------------------------------------------- /ObjectGraph/ObjectGraph.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObjectGraph.m 3 | // ObjectGraph 4 | // 5 | // Created by vampirewalk on 2015/1/13. 6 | // Copyright (c) 2015年 mocacube. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | // IN THE SOFTWARE. 25 | 26 | #import "ObjectGraph.h" 27 | #import "VWKShellHandler.h" 28 | #import "VWKWorkspaceManager.h" 29 | #import "VWKProject.h" 30 | 31 | static ObjectGraph *sharedPlugin; 32 | 33 | static NSString *BIN_PATH = @"/bin"; 34 | static NSString *USER_BIN_PATH = @"/usr/bin"; 35 | static NSString *USER_LOCAL_BIN_PATH = @"/usr/local/bin"; 36 | 37 | static NSString *PYTHON_EXECUTABLE = @"python"; 38 | static NSString *GRAPHVIZ_EXECUTABLE = @"dot"; 39 | static NSString *MOVE_EXECUTABLE = @"mv"; 40 | static NSString *OPEN_EXECUTABLE = @"open"; 41 | 42 | typedef void(^TaskBlock)(NSTask *t, NSString *standardOutputString, NSString *standardErrorString); 43 | 44 | @interface ObjectGraph() 45 | @property (nonatomic, strong, readwrite) NSBundle *bundle; 46 | @property (nonatomic, strong) NSMenuItem *drawObjectGraphItem; 47 | @property (nonatomic, strong) NSMenuItem *pathItem; 48 | @property (nonatomic, copy) NSString *sourceCodePath; 49 | @property (nonatomic, copy) TaskBlock getGraphvizExecutablePathBlock; 50 | @property (nonatomic, copy) TaskBlock convertToPDFBlock; 51 | @property (nonatomic, copy) TaskBlock movePDFFileBlock; 52 | @property (nonatomic, copy) TaskBlock moveDOTFileBlock; 53 | @property (nonatomic, copy) TaskBlock openBlock; 54 | 55 | @end 56 | 57 | @implementation ObjectGraph 58 | 59 | + (void)pluginDidLoad:(NSBundle *)plugin 60 | { 61 | static dispatch_once_t onceToken; 62 | NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"]; 63 | if ([currentApplicationName isEqual:@"Xcode"]) { 64 | dispatch_once(&onceToken, ^{ 65 | sharedPlugin = [[self alloc] initWithBundle:plugin]; 66 | }); 67 | } 68 | } 69 | 70 | + (instancetype)sharedPlugin 71 | { 72 | return sharedPlugin; 73 | } 74 | 75 | + (NSBundle *)pluginBundle 76 | { 77 | return [NSBundle bundleForClass:self]; 78 | } 79 | 80 | - (id)initWithBundle:(NSBundle *)plugin 81 | { 82 | if (self = [super init]) { 83 | // reference to plugin's bundle, for resource access 84 | self.bundle = plugin; 85 | 86 | [self setDefaultSourceCodePathPath]; 87 | 88 | [self setupGetGraphvizExecutablePathBlock]; 89 | [self setupConvertToPDFBlock]; 90 | [self setupMovePDFFileBlock]; 91 | [self setupMoveDOTFileBlock]; 92 | [self setupOpenBlock]; 93 | 94 | 95 | // Create menu items, initialize UI, etc. 96 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 97 | [self addMenuItems]; 98 | }]; 99 | } 100 | return self; 101 | } 102 | 103 | - (void)setDefaultSourceCodePathPath 104 | { 105 | VWKProject *project = [VWKProject projectForKeyWindow]; 106 | self.sourceCodePath = project.directoryPath; 107 | } 108 | 109 | 110 | #pragma mark - Menu 111 | 112 | - (void)addMenuItems 113 | { 114 | NSMenuItem *topMenuItem = [[NSApp mainMenu] itemWithTitle:@"Product"]; 115 | if (topMenuItem) { 116 | NSMenuItem *objectGraphMenu = [[NSMenuItem alloc] initWithTitle:@"ObjectGraph" action:nil keyEquivalent:@""]; 117 | objectGraphMenu.submenu = [[NSMenu alloc] initWithTitle:@"ObjectGraph"]; 118 | 119 | self.drawObjectGraphItem = [[NSMenuItem alloc] initWithTitle:@"Draw Object Graph" action:@selector(drawObjectGraph) keyEquivalent:@""]; 120 | [self.drawObjectGraphItem setTarget:self]; 121 | 122 | 123 | self.pathItem = [[NSMenuItem alloc] initWithTitle:@"Set Source Code PATH..." action:@selector(selectPath) keyEquivalent:@""]; 124 | [self.pathItem setTarget:self]; 125 | 126 | [[objectGraphMenu submenu] addItem:self.drawObjectGraphItem]; 127 | [[objectGraphMenu submenu] addItem:self.pathItem]; 128 | 129 | 130 | [[topMenuItem submenu] insertItem:objectGraphMenu atIndex:[topMenuItem.submenu indexOfItemWithTitle:@"Build For"]]; 131 | } 132 | } 133 | 134 | #pragma mark - Menu Actions 135 | 136 | // Sample Action, for menu item: 137 | - (void)drawObjectGraph 138 | { 139 | if([self isValidSourceCodePath]) 140 | { 141 | self.sourceCodePath = self.projectPath; 142 | } 143 | 144 | NSString *dotFileScriptPath = [[ObjectGraph pluginBundle] pathForResource:@"objc_dep" ofType:@"py"]; 145 | if (dotFileScriptPath.length) { 146 | [VWKShellHandler runShellCommand:[USER_BIN_PATH stringByAppendingPathComponent:PYTHON_EXECUTABLE] 147 | withArgs:@[dotFileScriptPath, _sourceCodePath, @"-o", self.dotFileName] 148 | directory:_sourceCodePath 149 | completion:self.getGraphvizExecutablePathBlock]; 150 | } 151 | } 152 | 153 | - (void)selectPath 154 | { 155 | NSOpenPanel *oPanel = [NSOpenPanel openPanel]; 156 | [oPanel setCanChooseDirectories:YES]; 157 | [oPanel setCanChooseFiles:NO]; 158 | if ([oPanel runModal] == NSModalResponseOK) { 159 | NSString *projPath = [[[oPanel URLs] objectAtIndex:0] path]; 160 | self.sourceCodePath = projPath; 161 | } 162 | } 163 | 164 | #pragma mark - Private Method 165 | 166 | - (NSString *)dotFileName 167 | { 168 | VWKProject *project = [VWKProject projectForKeyWindow]; 169 | NSString *dotFileName = [project.projectName stringByAppendingPathExtension:@"dot"]; 170 | return dotFileName; 171 | } 172 | 173 | - (NSString *)pdfFileName 174 | { 175 | VWKProject *project = [VWKProject projectForKeyWindow]; 176 | NSString *pdfFileName = [project.projectName stringByAppendingPathExtension:@"pdf"]; 177 | return pdfFileName; 178 | } 179 | 180 | - (NSString *)projectPath 181 | { 182 | VWKProject *project = [VWKProject projectForKeyWindow]; 183 | NSString *projectPath = project.directoryPath; 184 | return projectPath; 185 | } 186 | 187 | - (BOOL)isValidSourceCodePath 188 | { 189 | NSFileManager *manager = [NSFileManager defaultManager]; 190 | return _sourceCodePath == nil || ![manager fileExistsAtPath:_sourceCodePath]; 191 | } 192 | 193 | - (void)setupGetGraphvizExecutablePathBlock 194 | { 195 | NSDictionary *environmentDict = [[NSProcessInfo processInfo] environment]; 196 | NSString *shellString = [environmentDict objectForKey:@"SHELL"]; 197 | 198 | NSArray *args = @[@"-l", 199 | @"-c", 200 | @"which dot", //Assuming git is the launch path you want to run 201 | ]; 202 | __weak __typeof(&*self)weakSelf = self; 203 | self.getGraphvizExecutablePathBlock = ^(NSTask *t, 204 | NSString *standardOutputString, 205 | NSString *standardErrorString) 206 | { 207 | __strong __typeof(&*weakSelf)strongSelf = weakSelf; 208 | [VWKShellHandler runShellCommand:shellString 209 | withArgs:args 210 | directory:strongSelf.sourceCodePath 211 | completion:strongSelf.convertToPDFBlock]; 212 | }; 213 | } 214 | 215 | - (void)setupConvertToPDFBlock 216 | { 217 | __weak __typeof(&*self)weakSelf = self; 218 | self.convertToPDFBlock = ^(NSTask *t, 219 | NSString *standardOutputString, 220 | NSString *standardErrorString){ 221 | __strong __typeof(&*weakSelf)strongSelf = weakSelf; 222 | if (standardOutputString.length > 0) { 223 | NSString *GRAPHVIZ_EXECUTABLE_PATH = [standardOutputString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 224 | [VWKShellHandler runShellCommand:GRAPHVIZ_EXECUTABLE_PATH 225 | withArgs:@[@"-Tpdf", strongSelf.dotFileName, @"-o", strongSelf.pdfFileName] 226 | directory:strongSelf.sourceCodePath 227 | completion:strongSelf.movePDFFileBlock]; 228 | } 229 | }; 230 | } 231 | 232 | - (void)setupMovePDFFileBlock 233 | { 234 | __weak __typeof(&*self)weakSelf = self; 235 | self.movePDFFileBlock = ^(NSTask *t, 236 | NSString *standardOutputString, 237 | NSString *standardErrorString){ 238 | __strong __typeof(&*weakSelf)strongSelf = weakSelf; 239 | [VWKShellHandler runShellCommand:[BIN_PATH stringByAppendingPathComponent:MOVE_EXECUTABLE] 240 | withArgs:@[strongSelf.pdfFileName, strongSelf.projectPath] 241 | directory:strongSelf.sourceCodePath 242 | completion:strongSelf.moveDOTFileBlock]; 243 | }; 244 | } 245 | 246 | - (void)setupMoveDOTFileBlock 247 | { 248 | __weak __typeof(&*self)weakSelf = self; 249 | self.moveDOTFileBlock = ^(NSTask *t, 250 | NSString *standardOutputString, 251 | NSString *standardErrorString){ 252 | __strong __typeof(&*weakSelf)strongSelf = weakSelf; 253 | [VWKShellHandler runShellCommand:[BIN_PATH stringByAppendingPathComponent:MOVE_EXECUTABLE] 254 | withArgs:@[strongSelf.dotFileName, strongSelf.projectPath] 255 | directory:strongSelf.sourceCodePath 256 | completion:strongSelf.openBlock]; 257 | }; 258 | } 259 | 260 | - (void)setupOpenBlock 261 | { 262 | __weak __typeof(&*self)weakSelf = self; 263 | self.openBlock = ^(NSTask *t, 264 | NSString *standardOutputString, 265 | NSString *standardErrorString){ 266 | __strong __typeof(&*weakSelf)strongSelf = weakSelf; 267 | [VWKShellHandler runShellCommand:[USER_BIN_PATH stringByAppendingPathComponent:OPEN_EXECUTABLE] 268 | withArgs:@[strongSelf.pdfFileName] 269 | directory:strongSelf.projectPath 270 | completion:nil]; 271 | }; 272 | } 273 | 274 | - (void)dealloc 275 | { 276 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 277 | } 278 | 279 | @end 280 | -------------------------------------------------------------------------------- /ObjectGraph/VWKRunOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCPRunOperation.m 3 | // 4 | // Copyright (c) 2013 Delisa Mason. http://delisa.me 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | // IN THE SOFTWARE. 23 | 24 | #import "VWKRunOperation.h" 25 | 26 | @interface VWKRunOperation () 27 | { 28 | BOOL isExecuting; 29 | BOOL isFinished; 30 | } 31 | 32 | 33 | @property (retain) VWKXCodeConsole *xcodeConsole; 34 | 35 | @property (retain) NSTask *task; 36 | @property (retain) id taskStandardOutDataAvailableObserver; 37 | @property (retain) id taskStandardErrorDataAvailableObserver; 38 | @property (retain) id taskTerminationObserver; 39 | 40 | @end 41 | 42 | 43 | @implementation VWKRunOperation 44 | 45 | #pragma mark - 46 | #pragma mark NSOperation 47 | 48 | - (id)initWithTask:(NSTask *)task 49 | { 50 | self = [super init]; 51 | if (self) { 52 | self.xcodeConsole = [VWKXCodeConsole consoleForKeyWindow]; 53 | self.task = task; 54 | } 55 | return self; 56 | } 57 | 58 | - (instancetype)initWithTask:(NSTask *)task standardOutputString:(NSMutableString *) aStandardOutputString standardErrorString:(NSMutableString*) aStandardErrorString; 59 | { 60 | self = [super init]; 61 | if (self) { 62 | self.xcodeConsole = [VWKXCodeConsole consoleForKeyWindow]; 63 | self.task = task; 64 | self.standardOutputString = aStandardOutputString; 65 | self.standardErrorString = aStandardErrorString; 66 | } 67 | return self; 68 | } 69 | 70 | 71 | 72 | - (BOOL)isExecuting 73 | { 74 | return isExecuting; 75 | } 76 | 77 | - (void)setIsExecuting:(BOOL)_isExecuting 78 | { 79 | [self willChangeValueForKey:@"isExecuting"]; 80 | isExecuting = _isExecuting; 81 | [self didChangeValueForKey:@"isExecuting"]; 82 | } 83 | 84 | - (BOOL)isFinished 85 | { 86 | return isFinished; 87 | } 88 | 89 | - (void)setIsFinished:(BOOL)_isFinished 90 | { 91 | [self willChangeValueForKey:@"isFinished"]; 92 | isFinished = _isFinished; 93 | [self didChangeValueForKey:@"isFinished"]; 94 | } 95 | 96 | - (void)start 97 | { 98 | if (self.isCancelled) { 99 | self.isFinished = YES; 100 | return; 101 | } 102 | 103 | if (!NSThread.isMainThread) { 104 | [self performSelector:@selector(start) onThread:NSThread.mainThread withObject:nil waitUntilDone:NO]; 105 | return; 106 | } 107 | 108 | self.isExecuting = YES; 109 | [self main]; 110 | } 111 | 112 | - (void)main 113 | { 114 | if (self.isCancelled) { 115 | self.isExecuting = NO; 116 | self.isFinished = YES; 117 | } else { 118 | [self runOperation]; 119 | } 120 | } 121 | 122 | #pragma mark - 123 | #pragma mark NSTask 124 | 125 | - (void)runOperation 126 | { 127 | @try 128 | { 129 | NSPipe *standardOutputPipe = NSPipe.pipe; 130 | self.task.standardOutput = standardOutputPipe; 131 | NSPipe *standardErrorPipe = NSPipe.pipe; 132 | self.task.standardError = standardErrorPipe; 133 | NSFileHandle *standardOutputFileHandle = standardOutputPipe.fileHandleForReading; 134 | NSFileHandle *standardErrorFileHandle = standardErrorPipe.fileHandleForReading; 135 | 136 | __block NSMutableString *standardOutputBuffer = [NSMutableString string]; 137 | __block NSMutableString *standardErrorBuffer = [NSMutableString string]; 138 | 139 | self.taskStandardOutDataAvailableObserver = [NSNotificationCenter.defaultCenter addObserverForName:NSFileHandleDataAvailableNotification 140 | object:standardOutputFileHandle queue:NSOperationQueue.mainQueue 141 | usingBlock: ^(NSNotification *notification) { 142 | NSFileHandle *fileHandle = notification.object; 143 | NSString *data = [[NSString alloc] initWithData:fileHandle.availableData encoding:NSUTF8StringEncoding]; 144 | if (data.length > 0) { 145 | [standardOutputBuffer appendString:data]; 146 | [fileHandle waitForDataInBackgroundAndNotify]; 147 | standardOutputBuffer = [self writePipeBuffer:standardOutputBuffer]; 148 | [self.standardOutputString appendString:data]; 149 | } else { 150 | [self appendLine:standardOutputBuffer]; 151 | self.standardOutputString = standardOutputBuffer; 152 | [NSNotificationCenter.defaultCenter removeObserver:self.taskStandardOutDataAvailableObserver]; 153 | self.taskStandardOutDataAvailableObserver = nil; 154 | [self checkAndSetFinished]; 155 | } 156 | }]; 157 | 158 | self.taskStandardErrorDataAvailableObserver = [NSNotificationCenter.defaultCenter addObserverForName:NSFileHandleDataAvailableNotification 159 | object:standardErrorFileHandle queue:NSOperationQueue.mainQueue 160 | usingBlock: ^(NSNotification *notification) { 161 | NSFileHandle *fileHandle = notification.object; 162 | NSString *data = [[NSString alloc] initWithData:fileHandle.availableData encoding:NSUTF8StringEncoding]; 163 | if (data.length > 0) { 164 | [standardErrorBuffer appendString:data]; 165 | [fileHandle waitForDataInBackgroundAndNotify]; 166 | standardErrorBuffer = [self writePipeBuffer:standardErrorBuffer]; 167 | [self.standardErrorString appendString:data]; 168 | } else { 169 | [self appendLine:standardErrorBuffer]; 170 | self.standardErrorString = standardErrorBuffer; 171 | [NSNotificationCenter.defaultCenter removeObserver:self.taskStandardErrorDataAvailableObserver]; 172 | self.taskStandardErrorDataAvailableObserver = nil; 173 | [self checkAndSetFinished]; 174 | } 175 | }]; 176 | 177 | self.taskTerminationObserver = [NSNotificationCenter.defaultCenter addObserverForName:NSTaskDidTerminateNotification 178 | object:self.task queue:NSOperationQueue.mainQueue 179 | usingBlock: ^(NSNotification *notification) { 180 | [NSNotificationCenter.defaultCenter removeObserver:self.taskTerminationObserver]; 181 | self.taskTerminationObserver = nil; 182 | self.task = nil; 183 | [self checkAndSetFinished]; 184 | }]; 185 | 186 | [standardOutputFileHandle waitForDataInBackgroundAndNotify]; 187 | [standardErrorFileHandle waitForDataInBackgroundAndNotify]; 188 | 189 | [self.xcodeConsole appendText:[NSString stringWithFormat:@"%@ %@\n\n", self.task.launchPath, [self.task.arguments componentsJoinedByString:@" "]]]; 190 | [self.task launch]; 191 | } 192 | @catch (NSException *exception) 193 | { 194 | [self.xcodeConsole appendText:exception.description color:NSColor.redColor]; 195 | [self.xcodeConsole appendText:@"\n"]; 196 | self.isExecuting = NO; 197 | self.isFinished = YES; 198 | } 199 | } 200 | 201 | - (NSMutableString *)writePipeBuffer:(NSMutableString *)buffer 202 | { 203 | NSArray *lines = [buffer componentsSeparatedByCharactersInSet:NSCharacterSet.newlineCharacterSet]; 204 | if (lines.count > 1) { 205 | for (int i = 0; i < lines.count - 1; i++) { 206 | NSString *line = lines[i]; 207 | [self appendLine:line]; 208 | } 209 | return [lines[lines.count - 1] mutableCopy]; 210 | } 211 | return buffer; 212 | } 213 | 214 | - (void)appendLine:(NSString *)line 215 | { 216 | NSColor *color; 217 | 218 | if ([line hasPrefix:@"ERROR"]) 219 | color = NSColor.redColor; 220 | else if ([line hasPrefix:@"WARNING"]) 221 | color = NSColor.orangeColor; 222 | else if ([line hasPrefix:@"DEBUG"]) 223 | color = NSColor.grayColor; 224 | else if ([line hasPrefix:@"INFO"]) 225 | color = [NSColor colorWithDeviceRed:0.0 green:0.5 blue:0.0 alpha:1.0]; 226 | 227 | [self.xcodeConsole appendText:[line stringByAppendingString:@"\n"] color:color]; 228 | } 229 | 230 | - (void)checkAndSetFinished 231 | { 232 | if (self.taskStandardOutDataAvailableObserver == nil 233 | && self.taskStandardErrorDataAvailableObserver == nil 234 | && self.task == nil) { 235 | self.isExecuting = NO; 236 | self.isFinished = YES; 237 | } 238 | } 239 | 240 | @end 241 | -------------------------------------------------------------------------------- /ObjectGraph-Xcode.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 31F31C121A66047000E0D942 /* objc_dep.py in Resources */ = {isa = PBXBuildFile; fileRef = 31F31C101A66047000E0D942 /* objc_dep.py */; }; 11 | F83F75CF1A654E0100CC8DE3 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F83F75CE1A654E0100CC8DE3 /* AppKit.framework */; }; 12 | F83F75D11A654E0100CC8DE3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F83F75D01A654E0100CC8DE3 /* Foundation.framework */; }; 13 | F83F75D91A654E0100CC8DE3 /* ObjectGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = F83F75D81A654E0100CC8DE3 /* ObjectGraph.m */; }; 14 | F83F75EC1A654E5A00CC8DE3 /* VWKDocumentationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F83F75E01A654E5A00CC8DE3 /* VWKDocumentationManager.m */; }; 15 | F83F75ED1A654E5A00CC8DE3 /* VWKProject.m in Sources */ = {isa = PBXBuildFile; fileRef = F83F75E21A654E5A00CC8DE3 /* VWKProject.m */; }; 16 | F83F75EE1A654E5A00CC8DE3 /* VWKRunOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F83F75E41A654E5A00CC8DE3 /* VWKRunOperation.m */; }; 17 | F83F75EF1A654E5A00CC8DE3 /* VWKShellHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = F83F75E61A654E5A00CC8DE3 /* VWKShellHandler.m */; }; 18 | F83F75F01A654E5A00CC8DE3 /* VWKWorkspaceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F83F75E81A654E5A00CC8DE3 /* VWKWorkspaceManager.m */; }; 19 | F83F75F11A654E5A00CC8DE3 /* VWKXCodeConsole.m in Sources */ = {isa = PBXBuildFile; fileRef = F83F75EA1A654E5A00CC8DE3 /* VWKXCodeConsole.m */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 31F31C101A66047000E0D942 /* objc_dep.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = objc_dep.py; sourceTree = ""; }; 24 | 31F31C111A66047000E0D942 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 25 | F83F75CB1A654E0100CC8DE3 /* ObjectGraph.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ObjectGraph.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | F83F75CE1A654E0100CC8DE3 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 27 | F83F75D01A654E0100CC8DE3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 28 | F83F75D41A654E0100CC8DE3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | F83F75D51A654E0100CC8DE3 /* ObjectGraph.xcscheme */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = ObjectGraph.xcscheme; path = ObjectGraph.xcodeproj/xcshareddata/xcschemes/ObjectGraph.xcscheme; sourceTree = SOURCE_ROOT; }; 30 | F83F75D71A654E0100CC8DE3 /* ObjectGraph.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjectGraph.h; sourceTree = ""; }; 31 | F83F75D81A654E0100CC8DE3 /* ObjectGraph.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjectGraph.m; sourceTree = ""; }; 32 | F83F75DF1A654E5A00CC8DE3 /* VWKDocumentationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VWKDocumentationManager.h; sourceTree = ""; }; 33 | F83F75E01A654E5A00CC8DE3 /* VWKDocumentationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VWKDocumentationManager.m; sourceTree = ""; }; 34 | F83F75E11A654E5A00CC8DE3 /* VWKProject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VWKProject.h; sourceTree = ""; }; 35 | F83F75E21A654E5A00CC8DE3 /* VWKProject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VWKProject.m; sourceTree = ""; }; 36 | F83F75E31A654E5A00CC8DE3 /* VWKRunOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VWKRunOperation.h; sourceTree = ""; }; 37 | F83F75E41A654E5A00CC8DE3 /* VWKRunOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VWKRunOperation.m; sourceTree = ""; }; 38 | F83F75E51A654E5A00CC8DE3 /* VWKShellHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VWKShellHandler.h; sourceTree = ""; }; 39 | F83F75E61A654E5A00CC8DE3 /* VWKShellHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VWKShellHandler.m; sourceTree = ""; }; 40 | F83F75E71A654E5A00CC8DE3 /* VWKWorkspaceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VWKWorkspaceManager.h; sourceTree = ""; }; 41 | F83F75E81A654E5A00CC8DE3 /* VWKWorkspaceManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VWKWorkspaceManager.m; sourceTree = ""; }; 42 | F83F75E91A654E5A00CC8DE3 /* VWKXCodeConsole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VWKXCodeConsole.h; sourceTree = ""; }; 43 | F83F75EA1A654E5A00CC8DE3 /* VWKXCodeConsole.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VWKXCodeConsole.m; sourceTree = ""; }; 44 | F83F75EB1A654E5A00CC8DE3 /* ObjectGraph-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ObjectGraph-Prefix.pch"; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | F83F75C91A654E0100CC8DE3 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | F83F75CF1A654E0100CC8DE3 /* AppKit.framework in Frameworks */, 53 | F83F75D11A654E0100CC8DE3 /* Foundation.framework in Frameworks */, 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXFrameworksBuildPhase section */ 58 | 59 | /* Begin PBXGroup section */ 60 | 31F31C0F1A66047000E0D942 /* objc_dep */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 31F31C101A66047000E0D942 /* objc_dep.py */, 64 | 31F31C111A66047000E0D942 /* README.md */, 65 | ); 66 | path = objc_dep; 67 | sourceTree = ""; 68 | }; 69 | F83F75C21A654E0100CC8DE3 = { 70 | isa = PBXGroup; 71 | children = ( 72 | F83F75D21A654E0100CC8DE3 /* ObjectGraph */, 73 | F83F75CD1A654E0100CC8DE3 /* Frameworks */, 74 | F83F75CC1A654E0100CC8DE3 /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | F83F75CC1A654E0100CC8DE3 /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | F83F75CB1A654E0100CC8DE3 /* ObjectGraph.xcplugin */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | F83F75CD1A654E0100CC8DE3 /* Frameworks */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | F83F75CE1A654E0100CC8DE3 /* AppKit.framework */, 90 | F83F75D01A654E0100CC8DE3 /* Foundation.framework */, 91 | ); 92 | name = Frameworks; 93 | sourceTree = ""; 94 | }; 95 | F83F75D21A654E0100CC8DE3 /* ObjectGraph */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 31F31C0F1A66047000E0D942 /* objc_dep */, 99 | F83F75F21A654E6700CC8DE3 /* Helper */, 100 | F83F75D71A654E0100CC8DE3 /* ObjectGraph.h */, 101 | F83F75D81A654E0100CC8DE3 /* ObjectGraph.m */, 102 | F83F75D31A654E0100CC8DE3 /* Supporting Files */, 103 | ); 104 | path = ObjectGraph; 105 | sourceTree = ""; 106 | }; 107 | F83F75D31A654E0100CC8DE3 /* Supporting Files */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | F83F75EB1A654E5A00CC8DE3 /* ObjectGraph-Prefix.pch */, 111 | F83F75D41A654E0100CC8DE3 /* Info.plist */, 112 | F83F75D51A654E0100CC8DE3 /* ObjectGraph.xcscheme */, 113 | ); 114 | name = "Supporting Files"; 115 | sourceTree = ""; 116 | }; 117 | F83F75F21A654E6700CC8DE3 /* Helper */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | F83F75DF1A654E5A00CC8DE3 /* VWKDocumentationManager.h */, 121 | F83F75E01A654E5A00CC8DE3 /* VWKDocumentationManager.m */, 122 | F83F75E11A654E5A00CC8DE3 /* VWKProject.h */, 123 | F83F75E21A654E5A00CC8DE3 /* VWKProject.m */, 124 | F83F75E31A654E5A00CC8DE3 /* VWKRunOperation.h */, 125 | F83F75E41A654E5A00CC8DE3 /* VWKRunOperation.m */, 126 | F83F75E51A654E5A00CC8DE3 /* VWKShellHandler.h */, 127 | F83F75E61A654E5A00CC8DE3 /* VWKShellHandler.m */, 128 | F83F75E71A654E5A00CC8DE3 /* VWKWorkspaceManager.h */, 129 | F83F75E81A654E5A00CC8DE3 /* VWKWorkspaceManager.m */, 130 | F83F75E91A654E5A00CC8DE3 /* VWKXCodeConsole.h */, 131 | F83F75EA1A654E5A00CC8DE3 /* VWKXCodeConsole.m */, 132 | ); 133 | name = Helper; 134 | sourceTree = ""; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | F83F75CA1A654E0100CC8DE3 /* ObjectGraph */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = F83F75DC1A654E0100CC8DE3 /* Build configuration list for PBXNativeTarget "ObjectGraph" */; 142 | buildPhases = ( 143 | F83F75C71A654E0100CC8DE3 /* Sources */, 144 | F83F75C81A654E0100CC8DE3 /* Resources */, 145 | F83F75C91A654E0100CC8DE3 /* Frameworks */, 146 | ); 147 | buildRules = ( 148 | ); 149 | dependencies = ( 150 | ); 151 | name = ObjectGraph; 152 | productName = ObjectGraph; 153 | productReference = F83F75CB1A654E0100CC8DE3 /* ObjectGraph.xcplugin */; 154 | productType = "com.apple.product-type.bundle"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | F83F75C31A654E0100CC8DE3 /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | LastUpgradeCheck = 0610; 163 | ORGANIZATIONNAME = mocacube; 164 | TargetAttributes = { 165 | F83F75CA1A654E0100CC8DE3 = { 166 | CreatedOnToolsVersion = 6.1.1; 167 | }; 168 | }; 169 | }; 170 | buildConfigurationList = F83F75C61A654E0100CC8DE3 /* Build configuration list for PBXProject "ObjectGraph-Xcode" */; 171 | compatibilityVersion = "Xcode 3.2"; 172 | developmentRegion = English; 173 | hasScannedForEncodings = 0; 174 | knownRegions = ( 175 | en, 176 | ); 177 | mainGroup = F83F75C21A654E0100CC8DE3; 178 | productRefGroup = F83F75CC1A654E0100CC8DE3 /* Products */; 179 | projectDirPath = ""; 180 | projectRoot = ""; 181 | targets = ( 182 | F83F75CA1A654E0100CC8DE3 /* ObjectGraph */, 183 | ); 184 | }; 185 | /* End PBXProject section */ 186 | 187 | /* Begin PBXResourcesBuildPhase section */ 188 | F83F75C81A654E0100CC8DE3 /* Resources */ = { 189 | isa = PBXResourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | 31F31C121A66047000E0D942 /* objc_dep.py in Resources */, 193 | ); 194 | runOnlyForDeploymentPostprocessing = 0; 195 | }; 196 | /* End PBXResourcesBuildPhase section */ 197 | 198 | /* Begin PBXSourcesBuildPhase section */ 199 | F83F75C71A654E0100CC8DE3 /* Sources */ = { 200 | isa = PBXSourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | F83F75F01A654E5A00CC8DE3 /* VWKWorkspaceManager.m in Sources */, 204 | F83F75EE1A654E5A00CC8DE3 /* VWKRunOperation.m in Sources */, 205 | F83F75EF1A654E5A00CC8DE3 /* VWKShellHandler.m in Sources */, 206 | F83F75F11A654E5A00CC8DE3 /* VWKXCodeConsole.m in Sources */, 207 | F83F75D91A654E0100CC8DE3 /* ObjectGraph.m in Sources */, 208 | F83F75EC1A654E5A00CC8DE3 /* VWKDocumentationManager.m in Sources */, 209 | F83F75ED1A654E5A00CC8DE3 /* VWKProject.m in Sources */, 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | /* End PBXSourcesBuildPhase section */ 214 | 215 | /* Begin XCBuildConfiguration section */ 216 | F83F75DA1A654E0100CC8DE3 /* Debug */ = { 217 | isa = XCBuildConfiguration; 218 | buildSettings = { 219 | ALWAYS_SEARCH_USER_PATHS = NO; 220 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 221 | CLANG_CXX_LIBRARY = "libc++"; 222 | CLANG_ENABLE_MODULES = YES; 223 | CLANG_ENABLE_OBJC_ARC = YES; 224 | CLANG_WARN_BOOL_CONVERSION = YES; 225 | CLANG_WARN_CONSTANT_CONVERSION = YES; 226 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 227 | CLANG_WARN_EMPTY_BODY = YES; 228 | CLANG_WARN_ENUM_CONVERSION = YES; 229 | CLANG_WARN_INT_CONVERSION = YES; 230 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 231 | CLANG_WARN_UNREACHABLE_CODE = YES; 232 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 233 | COPY_PHASE_STRIP = NO; 234 | ENABLE_STRICT_OBJC_MSGSEND = YES; 235 | GCC_C_LANGUAGE_STANDARD = gnu99; 236 | GCC_DYNAMIC_NO_PIC = NO; 237 | GCC_OPTIMIZATION_LEVEL = 0; 238 | GCC_PREPROCESSOR_DEFINITIONS = ( 239 | "DEBUG=1", 240 | "$(inherited)", 241 | ); 242 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 243 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 244 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 245 | GCC_WARN_UNDECLARED_SELECTOR = YES; 246 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 247 | GCC_WARN_UNUSED_FUNCTION = YES; 248 | GCC_WARN_UNUSED_VARIABLE = YES; 249 | MTL_ENABLE_DEBUG_INFO = YES; 250 | ONLY_ACTIVE_ARCH = YES; 251 | }; 252 | name = Debug; 253 | }; 254 | F83F75DB1A654E0100CC8DE3 /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 259 | CLANG_CXX_LIBRARY = "libc++"; 260 | CLANG_ENABLE_MODULES = YES; 261 | CLANG_ENABLE_OBJC_ARC = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_CONSTANT_CONVERSION = YES; 264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 265 | CLANG_WARN_EMPTY_BODY = YES; 266 | CLANG_WARN_ENUM_CONVERSION = YES; 267 | CLANG_WARN_INT_CONVERSION = YES; 268 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 269 | CLANG_WARN_UNREACHABLE_CODE = YES; 270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 271 | COPY_PHASE_STRIP = YES; 272 | ENABLE_NS_ASSERTIONS = NO; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu99; 275 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 276 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 277 | GCC_WARN_UNDECLARED_SELECTOR = YES; 278 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 279 | GCC_WARN_UNUSED_FUNCTION = YES; 280 | GCC_WARN_UNUSED_VARIABLE = YES; 281 | MTL_ENABLE_DEBUG_INFO = NO; 282 | }; 283 | name = Release; 284 | }; 285 | F83F75DD1A654E0100CC8DE3 /* Debug */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | COMBINE_HIDPI_IMAGES = YES; 289 | DEPLOYMENT_LOCATION = YES; 290 | DSTROOT = "$(HOME)"; 291 | GCC_PREFIX_HEADER = "ObjectGraph/ObjectGraph-Prefix.pch"; 292 | INFOPLIST_FILE = ObjectGraph/Info.plist; 293 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 294 | MACOSX_DEPLOYMENT_TARGET = 10.10; 295 | PRODUCT_NAME = "$(TARGET_NAME)"; 296 | WRAPPER_EXTENSION = xcplugin; 297 | }; 298 | name = Debug; 299 | }; 300 | F83F75DE1A654E0100CC8DE3 /* Release */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | COMBINE_HIDPI_IMAGES = YES; 304 | DEPLOYMENT_LOCATION = YES; 305 | DSTROOT = "$(HOME)"; 306 | GCC_PREFIX_HEADER = "ObjectGraph/ObjectGraph-Prefix.pch"; 307 | INFOPLIST_FILE = ObjectGraph/Info.plist; 308 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 309 | MACOSX_DEPLOYMENT_TARGET = 10.10; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | WRAPPER_EXTENSION = xcplugin; 312 | }; 313 | name = Release; 314 | }; 315 | /* End XCBuildConfiguration section */ 316 | 317 | /* Begin XCConfigurationList section */ 318 | F83F75C61A654E0100CC8DE3 /* Build configuration list for PBXProject "ObjectGraph-Xcode" */ = { 319 | isa = XCConfigurationList; 320 | buildConfigurations = ( 321 | F83F75DA1A654E0100CC8DE3 /* Debug */, 322 | F83F75DB1A654E0100CC8DE3 /* Release */, 323 | ); 324 | defaultConfigurationIsVisible = 0; 325 | defaultConfigurationName = Release; 326 | }; 327 | F83F75DC1A654E0100CC8DE3 /* Build configuration list for PBXNativeTarget "ObjectGraph" */ = { 328 | isa = XCConfigurationList; 329 | buildConfigurations = ( 330 | F83F75DD1A654E0100CC8DE3 /* Debug */, 331 | F83F75DE1A654E0100CC8DE3 /* Release */, 332 | ); 333 | defaultConfigurationIsVisible = 0; 334 | defaultConfigurationName = Release; 335 | }; 336 | /* End XCConfigurationList section */ 337 | }; 338 | rootObject = F83F75C31A654E0100CC8DE3 /* Project object */; 339 | } 340 | --------------------------------------------------------------------------------