├── JDPluginManager.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── JDPluginManager ├── Classes │ ├── JDGitCloneTask.h │ ├── JDGitCloneTask.m │ ├── JDInstallProgressWindow.h │ ├── JDInstallProgressWindow.m │ ├── JDPluginInstaller.h │ ├── JDPluginInstaller.m │ ├── JDPluginManager.h │ ├── JDPluginManager.m │ ├── JDPluginMetaData.h │ ├── JDPluginMetaData.m │ ├── JDXcodeBuildTask.h │ ├── JDXcodeBuildTask.m │ ├── NSFileManager+JDPluginManager.h │ ├── NSFileManager+JDPluginManager.m │ ├── NSTaskWithProgress.h │ ├── NSTaskWithProgress.m │ ├── NSURL+JDPluginManager.h │ └── NSURL+JDPluginManager.m ├── ExampleProject │ ├── ExampleProject.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ └── ExampleProject │ │ ├── ExampleProject-Info.plist │ │ ├── ExampleProject-Prefix.pch │ │ ├── MainMenu.xib │ │ ├── TAAppDelegate.h │ │ ├── TAAppDelegate.m │ │ └── main.m ├── Info.plist ├── Localizable.strings └── global.h ├── README.md └── assets ├── screenshot1.png ├── screenshot2.png └── screenshot3.png /JDPluginManager.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7F2EB89C145057F200E97A87 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F2EB899145057EA00E97A87 /* AppKit.framework */; }; 11 | D22B0BB316D4310D0035D03B /* Localizable.strings in CopyFiles */ = {isa = PBXBuildFile; fileRef = D2E2D2E216D42CE900447430 /* Localizable.strings */; }; 12 | D237994016EBB62C006F9412 /* JDGitCloneTask.m in Sources */ = {isa = PBXBuildFile; fileRef = D237993116EBB62C006F9412 /* JDGitCloneTask.m */; }; 13 | D237994116EBB62C006F9412 /* JDInstallProgressWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = D237993316EBB62C006F9412 /* JDInstallProgressWindow.m */; }; 14 | D237994216EBB62C006F9412 /* JDPluginInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = D237993516EBB62C006F9412 /* JDPluginInstaller.m */; }; 15 | D237994316EBB62C006F9412 /* JDPluginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D237993716EBB62C006F9412 /* JDPluginManager.m */; }; 16 | D237994416EBB62C006F9412 /* JDXcodeBuildTask.m in Sources */ = {isa = PBXBuildFile; fileRef = D237993916EBB62C006F9412 /* JDXcodeBuildTask.m */; }; 17 | D237994616EBB62C006F9412 /* NSTaskWithProgress.m in Sources */ = {isa = PBXBuildFile; fileRef = D237993D16EBB62C006F9412 /* NSTaskWithProgress.m */; }; 18 | D237994716EBB62C006F9412 /* NSURL+JDPluginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D237993F16EBB62C006F9412 /* NSURL+JDPluginManager.m */; }; 19 | D23E31C116EC0F90001B2086 /* NSFileManager+JDPluginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D23E31C016EC0F90001B2086 /* NSFileManager+JDPluginManager.m */; }; 20 | D23E31C416EC0FA3001B2086 /* JDPluginMetaData.m in Sources */ = {isa = PBXBuildFile; fileRef = D23E31C316EC0FA3001B2086 /* JDPluginMetaData.m */; }; 21 | DA1B5D020E64686800921439 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 089C1672FE841209C02AAC07 /* Foundation.framework */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXCopyFilesBuildPhase section */ 25 | D277852A16C0589C001711DE /* CopyFiles */ = { 26 | isa = PBXCopyFilesBuildPhase; 27 | buildActionMask = 2147483647; 28 | dstPath = ""; 29 | dstSubfolderSpec = 7; 30 | files = ( 31 | D22B0BB316D4310D0035D03B /* Localizable.strings in CopyFiles */, 32 | ); 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXCopyFilesBuildPhase section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 089C1672FE841209C02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 39 | 7F2EB899145057EA00E97A87 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 40 | 8D5B49B6048680CD000E48DA /* JDPluginManager.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JDPluginManager.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | D237993016EBB62C006F9412 /* JDGitCloneTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDGitCloneTask.h; sourceTree = ""; }; 42 | D237993116EBB62C006F9412 /* JDGitCloneTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDGitCloneTask.m; sourceTree = ""; }; 43 | D237993216EBB62C006F9412 /* JDInstallProgressWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDInstallProgressWindow.h; sourceTree = ""; }; 44 | D237993316EBB62C006F9412 /* JDInstallProgressWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDInstallProgressWindow.m; sourceTree = ""; }; 45 | D237993416EBB62C006F9412 /* JDPluginInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDPluginInstaller.h; sourceTree = ""; }; 46 | D237993516EBB62C006F9412 /* JDPluginInstaller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDPluginInstaller.m; sourceTree = ""; }; 47 | D237993616EBB62C006F9412 /* JDPluginManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDPluginManager.h; sourceTree = ""; }; 48 | D237993716EBB62C006F9412 /* JDPluginManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDPluginManager.m; sourceTree = ""; }; 49 | D237993816EBB62C006F9412 /* JDXcodeBuildTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDXcodeBuildTask.h; sourceTree = ""; }; 50 | D237993916EBB62C006F9412 /* JDXcodeBuildTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDXcodeBuildTask.m; sourceTree = ""; }; 51 | D237993C16EBB62C006F9412 /* NSTaskWithProgress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSTaskWithProgress.h; sourceTree = ""; }; 52 | D237993D16EBB62C006F9412 /* NSTaskWithProgress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSTaskWithProgress.m; sourceTree = ""; }; 53 | D237993E16EBB62C006F9412 /* NSURL+JDPluginManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+JDPluginManager.h"; sourceTree = ""; }; 54 | D237993F16EBB62C006F9412 /* NSURL+JDPluginManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+JDPluginManager.m"; sourceTree = ""; }; 55 | D23E31BF16EC0F90001B2086 /* NSFileManager+JDPluginManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFileManager+JDPluginManager.h"; sourceTree = ""; }; 56 | D23E31C016EC0F90001B2086 /* NSFileManager+JDPluginManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager+JDPluginManager.m"; sourceTree = ""; }; 57 | D23E31C216EC0FA3001B2086 /* JDPluginMetaData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDPluginMetaData.h; sourceTree = ""; }; 58 | D23E31C316EC0FA3001B2086 /* JDPluginMetaData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDPluginMetaData.m; sourceTree = ""; }; 59 | D2E2D2E016D42CCC00447430 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = JDPluginManager/Info.plist; sourceTree = ""; }; 60 | D2E2D2E216D42CE900447430 /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = JDPluginManager/Localizable.strings; sourceTree = ""; }; 61 | D2E2D2E316D42CF800447430 /* global.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = global.h; path = JDPluginManager/global.h; sourceTree = SOURCE_ROOT; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFrameworksBuildPhase section */ 65 | 8D5B49B3048680CD000E48DA /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | DA1B5D020E64686800921439 /* Foundation.framework in Frameworks */, 70 | 7F2EB89C145057F200E97A87 /* AppKit.framework in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 089C166AFE841209C02AAC07 /* QuietXcode */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | D2E2D2E016D42CCC00447430 /* Info.plist */, 81 | D237992F16EBB62C006F9412 /* Classes */, 82 | 089C1671FE841209C02AAC07 /* Frameworks and Libraries */, 83 | 19C28FB8FE9D52D311CA2CBB /* Products */, 84 | ); 85 | name = QuietXcode; 86 | sourceTree = ""; 87 | }; 88 | 089C1671FE841209C02AAC07 /* Frameworks and Libraries */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 7F2EB899145057EA00E97A87 /* AppKit.framework */, 92 | 089C1672FE841209C02AAC07 /* Foundation.framework */, 93 | ); 94 | name = "Frameworks and Libraries"; 95 | sourceTree = ""; 96 | }; 97 | 089C167CFE841241C02AAC07 /* Localization */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | D2E2D2E316D42CF800447430 /* global.h */, 101 | D2E2D2E216D42CE900447430 /* Localizable.strings */, 102 | ); 103 | name = Localization; 104 | path = ../..; 105 | sourceTree = ""; 106 | }; 107 | 19C28FB8FE9D52D311CA2CBB /* Products */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 8D5B49B6048680CD000E48DA /* JDPluginManager.xcplugin */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | D237992F16EBB62C006F9412 /* Classes */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 089C167CFE841241C02AAC07 /* Localization */, 119 | D237994B16EBB6BE006F9412 /* Plugin Logic */, 120 | D237994916EBB676006F9412 /* UI */, 121 | D237994A16EBB69E006F9412 /* Extensions */, 122 | D237994816EBB665006F9412 /* Tasks */, 123 | ); 124 | name = Classes; 125 | path = JDPluginManager/Classes; 126 | sourceTree = ""; 127 | }; 128 | D237994816EBB665006F9412 /* Tasks */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | D237993016EBB62C006F9412 /* JDGitCloneTask.h */, 132 | D237993116EBB62C006F9412 /* JDGitCloneTask.m */, 133 | D237993816EBB62C006F9412 /* JDXcodeBuildTask.h */, 134 | D237993916EBB62C006F9412 /* JDXcodeBuildTask.m */, 135 | D237993C16EBB62C006F9412 /* NSTaskWithProgress.h */, 136 | D237993D16EBB62C006F9412 /* NSTaskWithProgress.m */, 137 | ); 138 | name = Tasks; 139 | sourceTree = ""; 140 | }; 141 | D237994916EBB676006F9412 /* UI */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | D237993216EBB62C006F9412 /* JDInstallProgressWindow.h */, 145 | D237993316EBB62C006F9412 /* JDInstallProgressWindow.m */, 146 | ); 147 | name = UI; 148 | sourceTree = ""; 149 | }; 150 | D237994A16EBB69E006F9412 /* Extensions */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | D237993E16EBB62C006F9412 /* NSURL+JDPluginManager.h */, 154 | D237993F16EBB62C006F9412 /* NSURL+JDPluginManager.m */, 155 | D23E31BF16EC0F90001B2086 /* NSFileManager+JDPluginManager.h */, 156 | D23E31C016EC0F90001B2086 /* NSFileManager+JDPluginManager.m */, 157 | ); 158 | name = Extensions; 159 | sourceTree = ""; 160 | }; 161 | D237994B16EBB6BE006F9412 /* Plugin Logic */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | D237993416EBB62C006F9412 /* JDPluginInstaller.h */, 165 | D237993516EBB62C006F9412 /* JDPluginInstaller.m */, 166 | D237993616EBB62C006F9412 /* JDPluginManager.h */, 167 | D237993716EBB62C006F9412 /* JDPluginManager.m */, 168 | D23E31C216EC0FA3001B2086 /* JDPluginMetaData.h */, 169 | D23E31C316EC0FA3001B2086 /* JDPluginMetaData.m */, 170 | ); 171 | name = "Plugin Logic"; 172 | sourceTree = ""; 173 | }; 174 | /* End PBXGroup section */ 175 | 176 | /* Begin PBXNativeTarget section */ 177 | 8D5B49AC048680CD000E48DA /* JDPluginManager */ = { 178 | isa = PBXNativeTarget; 179 | buildConfigurationList = 1DEB913A08733D840010E9CD /* Build configuration list for PBXNativeTarget "JDPluginManager" */; 180 | buildPhases = ( 181 | D277852A16C0589C001711DE /* CopyFiles */, 182 | 8D5B49B1048680CD000E48DA /* Sources */, 183 | 8D5B49B3048680CD000E48DA /* Frameworks */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | ); 189 | name = JDPluginManager; 190 | productInstallPath = "$(HOME)/Library/Bundles"; 191 | productName = QuietXcode; 192 | productReference = 8D5B49B6048680CD000E48DA /* JDPluginManager.xcplugin */; 193 | productType = "com.apple.product-type.bundle"; 194 | }; 195 | /* End PBXNativeTarget section */ 196 | 197 | /* Begin PBXProject section */ 198 | 089C1669FE841209C02AAC07 /* Project object */ = { 199 | isa = PBXProject; 200 | attributes = { 201 | LastUpgradeCheck = 0510; 202 | }; 203 | buildConfigurationList = 1DEB913E08733D840010E9CD /* Build configuration list for PBXProject "JDPluginManager" */; 204 | compatibilityVersion = "Xcode 3.2"; 205 | developmentRegion = English; 206 | hasScannedForEncodings = 1; 207 | knownRegions = ( 208 | English, 209 | Japanese, 210 | French, 211 | German, 212 | ); 213 | mainGroup = 089C166AFE841209C02AAC07 /* QuietXcode */; 214 | projectDirPath = ""; 215 | projectRoot = ""; 216 | targets = ( 217 | 8D5B49AC048680CD000E48DA /* JDPluginManager */, 218 | ); 219 | }; 220 | /* End PBXProject section */ 221 | 222 | /* Begin PBXSourcesBuildPhase section */ 223 | 8D5B49B1048680CD000E48DA /* Sources */ = { 224 | isa = PBXSourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | D237994016EBB62C006F9412 /* JDGitCloneTask.m in Sources */, 228 | D237994116EBB62C006F9412 /* JDInstallProgressWindow.m in Sources */, 229 | D237994216EBB62C006F9412 /* JDPluginInstaller.m in Sources */, 230 | D237994316EBB62C006F9412 /* JDPluginManager.m in Sources */, 231 | D237994416EBB62C006F9412 /* JDXcodeBuildTask.m in Sources */, 232 | D237994616EBB62C006F9412 /* NSTaskWithProgress.m in Sources */, 233 | D237994716EBB62C006F9412 /* NSURL+JDPluginManager.m in Sources */, 234 | D23E31C116EC0F90001B2086 /* NSFileManager+JDPluginManager.m in Sources */, 235 | D23E31C416EC0FA3001B2086 /* JDPluginMetaData.m in Sources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXSourcesBuildPhase section */ 240 | 241 | /* Begin XCBuildConfiguration section */ 242 | 1DEB913B08733D840010E9CD /* Debug */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_ENABLE_OBJC_ARC = YES; 247 | COMBINE_HIDPI_IMAGES = YES; 248 | COPY_PHASE_STRIP = NO; 249 | DEPLOYMENT_LOCATION = YES; 250 | DEPLOYMENT_POSTPROCESSING = YES; 251 | DSTROOT = "$(HOME)"; 252 | GCC_DYNAMIC_NO_PIC = NO; 253 | GCC_ENABLE_OBJC_GC = unsupported; 254 | GCC_MODEL_TUNING = G5; 255 | GCC_OPTIMIZATION_LEVEL = 0; 256 | INFOPLIST_FILE = JDPluginManager/Info.plist; 257 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 258 | LD_RUNPATH_SEARCH_PATHS = /Developer; 259 | PRODUCT_NAME = JDPluginManager; 260 | STRIP_INSTALLED_PRODUCT = NO; 261 | WRAPPER_EXTENSION = xcplugin; 262 | }; 263 | name = Debug; 264 | }; 265 | 1DEB913F08733D840010E9CD /* Debug */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | CURRENT_PROJECT_VERSION = 1.0.1; 269 | GCC_C_LANGUAGE_STANDARD = c99; 270 | GCC_OPTIMIZATION_LEVEL = 0; 271 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 272 | GCC_WARN_UNUSED_VARIABLE = YES; 273 | INSTALL_PATH = "$(HOME)/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 274 | MACOSX_DEPLOYMENT_TARGET = 10.7; 275 | ONLY_ACTIVE_ARCH = YES; 276 | SDKROOT = macosx; 277 | }; 278 | name = Debug; 279 | }; 280 | /* End XCBuildConfiguration section */ 281 | 282 | /* Begin XCConfigurationList section */ 283 | 1DEB913A08733D840010E9CD /* Build configuration list for PBXNativeTarget "JDPluginManager" */ = { 284 | isa = XCConfigurationList; 285 | buildConfigurations = ( 286 | 1DEB913B08733D840010E9CD /* Debug */, 287 | ); 288 | defaultConfigurationIsVisible = 0; 289 | defaultConfigurationName = Debug; 290 | }; 291 | 1DEB913E08733D840010E9CD /* Build configuration list for PBXProject "JDPluginManager" */ = { 292 | isa = XCConfigurationList; 293 | buildConfigurations = ( 294 | 1DEB913F08733D840010E9CD /* Debug */, 295 | ); 296 | defaultConfigurationIsVisible = 0; 297 | defaultConfigurationName = Debug; 298 | }; 299 | /* End XCConfigurationList section */ 300 | }; 301 | rootObject = 089C1669FE841209C02AAC07 /* Project object */; 302 | } 303 | -------------------------------------------------------------------------------- /JDPluginManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDGitCloneTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // JDGitCloneTask.h 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 09.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import "NSTaskWithProgress.h" 10 | 11 | extern NSString *const gitPath; 12 | extern NSString *const tmpClonePath; 13 | 14 | @class JDInstallProgressWindow; 15 | 16 | @interface JDGitCloneTask : NSTaskWithProgress { 17 | NSMutableString* _allGitOutput; 18 | NSString* _previousProgressText; 19 | } 20 | 21 | + (instancetype)launchedTaskWithRepositoryPath:(NSString*)repositoryPath 22 | progressWindow:(JDInstallProgressWindow*)progressWindow 23 | completion:(void(^)(NSString *clonePath))completion; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDGitCloneTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // JDGitCloneTask.m 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 09.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import "global.h" 10 | #import "JDInstallProgressWindow.h" 11 | 12 | #import "JDGitCloneTask.h" 13 | 14 | NSString *const gitPath = @"/usr/bin/git"; 15 | NSString *const tmpClonePath = @"/tmp/JDPluginManager/"; 16 | 17 | 18 | @interface JDGitCloneTask () 19 | @property (atomic, strong) NSMutableString *allGitOutput; 20 | @property (atomic, copy) NSString *previousProgressText; 21 | @end 22 | 23 | @implementation JDGitCloneTask 24 | 25 | @synthesize allGitOutput = _allGitOutput; 26 | @synthesize previousProgressText = _previousProgressText; 27 | 28 | + (instancetype)launchedTaskWithRepositoryPath:(NSString*)repositoryPath 29 | progressWindow:(JDInstallProgressWindow*)progressWindow 30 | completion:(void(^)(NSString *clonePath))completion 31 | { 32 | 33 | JDGitCloneTask *task = [[JDGitCloneTask alloc] initWithRepositoryPath:repositoryPath 34 | progressWindow:progressWindow 35 | completion:completion]; 36 | [task.task launch]; 37 | return task; 38 | } 39 | 40 | - (id)initWithRepositoryPath:(NSString*)repositoryPath 41 | progressWindow:(JDInstallProgressWindow*)progressWindow 42 | completion:(void(^)(NSString *clonePath))completion 43 | { 44 | [progressWindow appendTitle:JDLocalize(@"keyInstallCloneMessage")]; 45 | [progressWindow appendLine:[NSString stringWithFormat: @"Repository: %@\n", repositoryPath]]; 46 | 47 | NSString *clonePath = [tmpClonePath stringByAppendingPathComponent:[[repositoryPath lastPathComponent] stringByReplacingOccurrencesOfString:@".git" withString:@""]]; 48 | NSArray *gitArgs = @[@"clone", repositoryPath, clonePath, @"--progress", @"--verbose"]; 49 | 50 | self = [super initWithLaunchPath:gitPath arguments:gitArgs currentDirectoryPath:nil progress:^(NSTask *task, NSString *output) { 51 | [self handleGitOutput:output inProgressWindow:progressWindow]; 52 | } completion:^(NSTask *task, NSString *output) { 53 | [self handleGitOutput:output inProgressWindow:progressWindow]; 54 | if (completion) { 55 | completion(clonePath); 56 | } 57 | }]; 58 | 59 | if (self) { 60 | _allGitOutput = [[NSMutableString alloc] init]; 61 | _previousProgressText = [progressWindow.textView.string copy]; 62 | } 63 | 64 | return self; 65 | } 66 | 67 | 68 | #pragma mark handle output 69 | 70 | - (void)handleGitOutput:(NSString*)output 71 | inProgressWindow:(JDInstallProgressWindow*)progressWindow 72 | { 73 | [self.allGitOutput appendString:output]; 74 | 75 | progressWindow.textView.string = [NSString stringWithFormat: @"%@%@\n", 76 | self.previousProgressText, 77 | [self minimizedGitOutput]]; 78 | [progressWindow scrollToBottom]; 79 | } 80 | 81 | - (NSString*)minimizedGitOutput 82 | { 83 | NSMutableString *minimizedOutput = [NSMutableString string]; 84 | NSString *previousLine = nil; 85 | for (__strong NSString *line in [self.allGitOutput componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]) { 86 | line = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 87 | static NSInteger refLength = 15; 88 | if (previousLine && previousLine.length > refLength && line.length > refLength && 89 | [[previousLine substringToIndex:refLength] isEqualToString:[line substringToIndex:refLength]]) { 90 | [minimizedOutput replaceOccurrencesOfString:previousLine withString:line options:NSBackwardsSearch range:NSMakeRange(0, minimizedOutput.length)]; 91 | } else { 92 | [minimizedOutput appendFormat:@"%@\n", line]; 93 | } 94 | previousLine = line; 95 | } 96 | return [minimizedOutput stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 97 | } 98 | 99 | @end 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDInstallProgressWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // JDInstallProgressWindow.h 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 09.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface JDInstallProgressWindow : NSWindow { 12 | NSTextView* _textView; 13 | } 14 | 15 | @property (nonatomic, readonly) NSTextView *textView; 16 | 17 | - (id)initWithContentRect:(NSRect)contentRect; 18 | 19 | - (void)appendLine:(NSString*)line; 20 | - (void)appendTitle:(NSString*)title; 21 | 22 | - (void)scrollToBottom; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDInstallProgressWindow.m: -------------------------------------------------------------------------------- 1 | // 2 | // JDInstallProgressWindow.m 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 09.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import "global.h" 10 | 11 | #import "JDInstallProgressWindow.h" 12 | 13 | @implementation JDInstallProgressWindow 14 | 15 | @synthesize textView = _textView; 16 | 17 | - (id)initWithContentRect:(NSRect)contentRect 18 | { 19 | self = [super initWithContentRect:contentRect styleMask:NSTitledWindowMask | NSMiniaturizableWindowMask 20 | backing:NSBackingStoreBuffered defer:NO]; 21 | if (self) { 22 | self.title = JDLocalize(@"keyInstallProgressTitle"); 23 | self.minSize = NSMakeSize(568, 320); 24 | self.backgroundColor = [NSColor colorWithCalibratedWhite:0 alpha:0.77]; 25 | self.opaque = NO; 26 | 27 | // setup scrollView 28 | NSView *contentView = self.contentView; 29 | NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:NSInsetRect(contentView.bounds, 10, 10)]; 30 | scrollView.backgroundColor = [NSColor colorWithCalibratedWhite:0 alpha:0.77]; 31 | scrollView.borderType = NSNoBorder; 32 | scrollView.hasVerticalScroller = YES; 33 | scrollView.hasHorizontalScroller = NO; 34 | scrollView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; 35 | [self.contentView addSubview:scrollView]; 36 | 37 | // setup textView 38 | NSSize contentSize = scrollView.contentSize; 39 | NSRect contentSizeRect = NSMakeRect(0, 0,contentSize.width, contentSize.height); 40 | NSTextView *textView = [[NSTextView alloc] initWithFrame:contentSizeRect]; 41 | textView.autoresizingMask = NSViewWidthSizable; 42 | textView.minSize = contentSize; 43 | textView.maxSize = NSMakeSize(FLT_MAX, FLT_MAX); 44 | textView.verticallyResizable = YES; 45 | textView.horizontallyResizable = YES; 46 | textView.selectable = NO; 47 | textView.editable = NO; 48 | textView.backgroundColor = [NSColor clearColor]; 49 | textView.alignment = NSLeftTextAlignment; 50 | textView.textColor = [NSColor whiteColor]; 51 | textView.font = [NSFont fontWithName:@"Monaco" size:11]; 52 | textView.textContainer.containerSize = NSMakeSize(contentSize.width, FLT_MAX); 53 | textView.textContainer.widthTracksTextView = YES; 54 | [scrollView addSubview:textView]; 55 | _textView = textView; 56 | 57 | // connect scrollView 58 | scrollView.documentView = textView; 59 | [self makeFirstResponder:textView]; 60 | } 61 | return self; 62 | } 63 | 64 | 65 | - (void)appendLine:(NSString*)line 66 | { 67 | if (line) { 68 | self.textView.string = [NSString stringWithFormat: @"%@%@\n", self.textView.string, line]; 69 | [self scrollToBottom]; 70 | } 71 | } 72 | 73 | - (void)appendTitle:(NSString*)title 74 | { 75 | NSString *topSeparator = @"----------------------------------------------------------- JDPluginManager -----"; 76 | NSString *bottomSeparator = @"---------------------------------------------------------------------------------"; 77 | 78 | // add newline before, if textView already contains text 79 | if (self.textView.string.length > 0) { 80 | topSeparator = [NSString stringWithFormat: @"\n%@", topSeparator]; 81 | } 82 | 83 | [self appendLine:topSeparator]; 84 | [self appendLine:title]; 85 | [self appendLine:bottomSeparator]; 86 | } 87 | 88 | - (void)scrollToBottom 89 | { 90 | [self.textView scrollRangeToVisible:NSMakeRange([self.textView.string length]-1,1)]; 91 | } 92 | 93 | @end 94 | 95 | 96 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDPluginInstaller.h: -------------------------------------------------------------------------------- 1 | // 2 | // JDPluginInstaller.h 3 | // JDPluginManager 4 | // 5 | // Created by Markus Emrich on 04.02.13. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class JDInstallProgressWindow; 13 | @class NSTaskWithProgress; 14 | 15 | @interface JDPluginInstaller : NSObject { 16 | NSTaskWithProgress* _activeTask; 17 | NSString* _repositoryPath; 18 | NSMutableArray* _pathsToBuild; 19 | JDInstallProgressWindow* _progressWindow; 20 | } 21 | 22 | + (void)installPlugin; 23 | 24 | - (void)beginInstallWithRepositoryPath:(NSString*)repositoryPath 25 | searchInSubdirectories:(BOOL)searchSubdirectories; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDPluginInstaller.m: -------------------------------------------------------------------------------- 1 | // 2 | // JDPluginInstaller.m 3 | // JDPluginManager 4 | // 5 | // Created by Markus Emrich on 04.02.13. 6 | // 7 | // 8 | 9 | #import "global.h" 10 | #import "JDGitCloneTask.h" 11 | #import "JDXcodeBuildTask.h" 12 | #import "JDInstallProgressWindow.h" 13 | #import "NSFileManager+JDPluginManager.h" 14 | #import "NSURL+JDPluginManager.h" 15 | #import "JDPluginMetaData.h" 16 | 17 | #import "JDPluginInstaller.h" 18 | 19 | 20 | @interface JDPluginInstaller () 21 | @property (nonatomic, strong) NSTaskWithProgress *activeTask; 22 | @property (nonatomic, copy) NSString *repositoryPath; 23 | @property (nonatomic, strong) NSMutableArray *pathsToBuild; 24 | @property (nonatomic, strong) JDInstallProgressWindow *progressWindow; 25 | + (BOOL)toolsAreAvailable; 26 | - (void)showInstallPrompt; 27 | - (void)showProgressPanel; 28 | - (void)startXcodeBuildWithCompletion:(void(^)(void))completion; 29 | - (void)emptyTempDirectory; 30 | @end 31 | 32 | @implementation JDPluginInstaller 33 | 34 | @synthesize activeTask = _activeTask; 35 | @synthesize repositoryPath = _repositoryPath; 36 | @synthesize pathsToBuild = _pathsToBuild; 37 | @synthesize progressWindow = _progressWindow; 38 | 39 | + (BOOL)toolsAreAvailable 40 | { 41 | NSString *errorTitle = nil, *errorMessage = nil; 42 | 43 | // check for xcodebuild 44 | BOOL xcodeBuildFound = [[NSFileManager defaultManager] fileExistsAtPath:xcodeBuildPath]; 45 | if (!xcodeBuildFound) { 46 | errorTitle = JDLocalize(@"keyErrorXcodeToolsMissingTitle"); 47 | errorMessage = JDLocalize(@"keyErrorXcodeToolsMissingMessage"); 48 | } 49 | 50 | // check for git 51 | BOOL gitFound = [[NSFileManager defaultManager] fileExistsAtPath:gitPath]; 52 | if (!gitFound) { 53 | errorTitle = JDLocalize(@"keyErrorGitMissingTitle"); 54 | errorMessage = JDLocalize(@"keyErrorGitMissingMessage"); 55 | } 56 | 57 | // display error message 58 | if (errorTitle != nil) { 59 | NSAlert *alert = [NSAlert alertWithMessageText:errorTitle 60 | defaultButton:JDLocalize(@"keyOK") 61 | alternateButton:nil 62 | otherButton:nil 63 | informativeTextWithFormat:@"%@", errorMessage]; 64 | alert.alertStyle = NSCriticalAlertStyle; 65 | [alert runModal]; 66 | return NO; 67 | } 68 | 69 | return YES; 70 | } 71 | 72 | + (void)installPlugin 73 | { 74 | if (![self toolsAreAvailable]) { 75 | return; 76 | } 77 | 78 | [[[self alloc] init] showInstallPrompt]; 79 | } 80 | 81 | - (void)showInstallPrompt 82 | { 83 | // setup input alert 84 | NSAlert *alert = [NSAlert alertWithMessageText:JDLocalize(@"keyInstallAlertTitleFormat") 85 | defaultButton:JDLocalize(@"keyInstall") 86 | alternateButton:JDLocalize(@"keyCancel") 87 | otherButton:nil 88 | informativeTextWithFormat:JDLocalize(@"keyInstallAlertMessage")]; 89 | alert.alertStyle = NSInformationalAlertStyle; 90 | 91 | // add text field 92 | NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 30, 460, 24)]; 93 | [input setStringValue:JDLocalize(@"keyInstallAlertExampleText")]; 94 | [input setBezeled:YES]; 95 | [input setBezelStyle:NSTextFieldRoundedBezel]; 96 | 97 | // add checkbox 98 | NSButton *checkbox = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 20, 20)]; 99 | [checkbox setButtonType:NSSwitchButton]; 100 | [checkbox setTitle:JDLocalize(@"keyInstallAlertCheckboxText")]; 101 | [checkbox sizeToFit]; 102 | 103 | // build accessory view 104 | NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 460, 50)]; 105 | [view addSubview:input]; 106 | [view addSubview:checkbox]; 107 | [alert setAccessoryView:view]; 108 | 109 | // show prompt 110 | NSInteger selectedButtonIndex = [alert runModal]; 111 | if (selectedButtonIndex == 0) { 112 | return; 113 | } 114 | 115 | // install 116 | BOOL searchSubdirectories = (checkbox.state == NSOnState); 117 | [self beginInstallWithRepositoryPath:input.stringValue searchInSubdirectories:searchSubdirectories]; 118 | } 119 | 120 | - (void)beginInstallWithRepositoryPath:(NSString*)repositoryPath searchInSubdirectories:(BOOL)searchSubdirectories 121 | { 122 | if (![JDPluginInstaller toolsAreAvailable]) { 123 | return; 124 | } 125 | 126 | // save repo URL 127 | self.repositoryPath = repositoryPath; 128 | 129 | // show progress panel 130 | [self showProgressPanel]; 131 | 132 | // move checked out project to trash 133 | [self emptyTempDirectory]; 134 | 135 | @try { 136 | // clone project 137 | self.activeTask = [JDGitCloneTask launchedTaskWithRepositoryPath:repositoryPath 138 | progressWindow:self.progressWindow 139 | completion:^(NSString *clonePath) 140 | { 141 | // use top level folder for install 142 | NSArray *pathsToCheck = @[clonePath]; 143 | 144 | // use subdirectories, if enabled 145 | if (searchSubdirectories) { 146 | NSMutableArray *array = [NSMutableArray array]; 147 | 148 | // search for actual directories 149 | NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:clonePath error:nil]; 150 | [contents enumerateObjectsUsingBlock:^(NSString *contentPath, NSUInteger idx, BOOL *stop) { 151 | BOOL isDirectory = NO; 152 | [[NSFileManager defaultManager] fileExistsAtPath:[clonePath stringByAppendingPathComponent:contentPath] isDirectory:&isDirectory]; 153 | if (isDirectory && ![contentPath hasPrefix:@"."] && ![contentPath hasSuffix:@".xcworkspace"]) { 154 | [array addObject:[clonePath stringByAppendingPathComponent:contentPath]]; 155 | } 156 | }]; 157 | // use subdirectories, if found 158 | if (array.count > 0) { 159 | pathsToCheck = array; 160 | } 161 | } 162 | 163 | // save paths 164 | self.pathsToBuild = [pathsToCheck mutableCopy]; 165 | 166 | // start xcode build 167 | [self startXcodeBuildWithCompletion:^{ 168 | // move checked out project(s) to trash 169 | [self.progressWindow appendLine:JDLocalize(@"keyInstallTrashingTmpDirectory")]; 170 | [self emptyTempDirectory]; 171 | 172 | // release task 173 | self.activeTask = nil; 174 | 175 | // close panel 176 | self.progressWindow.styleMask = self.progressWindow.styleMask | NSClosableWindowMask; 177 | }]; 178 | }]; 179 | } 180 | @catch (NSException *exception) { 181 | NSAlert *alert = [NSAlert alertWithMessageText:JDLocalize(@"keyInstallErrorTitle") 182 | defaultButton:JDLocalize(@"keyOK") 183 | alternateButton:nil 184 | otherButton:nil 185 | informativeTextWithFormat:JDLocalize(@"keyInstallErrorMessage")]; 186 | alert.alertStyle = NSCriticalAlertStyle; 187 | [alert runModal]; 188 | } 189 | } 190 | 191 | - (void)startXcodeBuildWithCompletion:(void(^)(void))completion 192 | { 193 | // run xcodebuild for each path given 194 | if (self.pathsToBuild.count > 0) { 195 | NSString *path = [self.pathsToBuild.lastObject copy]; 196 | [self.pathsToBuild removeObject:path]; 197 | 198 | // directoryContentsBefore 199 | NSArray *directoryContentsBefore = [NSFileManager allPluginsWithModifiedDate:YES]; 200 | 201 | completion = [completion copy]; 202 | 203 | self.activeTask = [JDXcodeBuildTask launchedTaskWithCurrentDirectoryPath:path 204 | progressWindow:self.progressWindow 205 | completion:^{ 206 | // directory contents after 207 | NSArray *directoryContentsAfter = [NSFileManager allPluginsWithModifiedDate:YES]; 208 | [self checkSuccessAndUpdateMetaDataWithContentsBefore:directoryContentsBefore 209 | andAfter:directoryContentsAfter 210 | withBuildPath:path]; 211 | 212 | // build next path, or finish 213 | [self startXcodeBuildWithCompletion:completion]; 214 | }]; 215 | } 216 | else { 217 | if (completion) { 218 | completion(); 219 | } 220 | } 221 | } 222 | 223 | - (void)checkSuccessAndUpdateMetaDataWithContentsBefore:(NSArray*)directoryContentsBefore 224 | andAfter:(NSArray*)directoryContentsAfter 225 | withBuildPath:(NSString*)buildPath 226 | { 227 | __block NSString *changedPlugin = nil; 228 | 229 | [directoryContentsBefore enumerateObjectsUsingBlock:^(NSDictionary *before, NSUInteger idx, BOOL *stop) { 230 | NSDictionary *after = [directoryContentsAfter objectAtIndex:idx]; 231 | NSString *beforeString = [NSString stringWithFormat: @"%@-%@", [before objectForKey:JDPluginNameKey], [before objectForKey:JDPluginModifiedDate]]; 232 | NSString *afterString = [NSString stringWithFormat: @"%@-%@", [after objectForKey:JDPluginNameKey], [after objectForKey:JDPluginModifiedDate]]; 233 | if (![beforeString isEqualToString:afterString]) { 234 | *stop = YES; 235 | changedPlugin = [after objectForKey:JDPluginNameKey]; 236 | } 237 | }]; 238 | 239 | if (changedPlugin == nil) { 240 | // inform user 241 | [self.progressWindow appendTitle:JDLocalize(@"keyInstallFailureMessage")]; 242 | } else { 243 | // save meta data 244 | NSString *pluginPath = [[NSURL pluginURLForPluginNamed:changedPlugin] path]; 245 | JDPluginMetaData *metaData = [[JDPluginMetaData alloc] initWithPluginPath:pluginPath]; 246 | [metaData findAndSetReadmeAtBuildPath:buildPath]; 247 | [metaData setObject:self.repositoryPath forKey:JDPluginManagerMetaDataRepositoryKey]; 248 | [metaData save]; 249 | 250 | // inform user 251 | [self.progressWindow appendTitle:[NSString stringWithFormat:JDLocalize(@"keyInstallSuccessMessageFormat"), changedPlugin]]; 252 | } 253 | } 254 | 255 | - (void)showProgressPanel 256 | { 257 | self.progressWindow = [[JDInstallProgressWindow alloc] initWithContentRect:NSMakeRect(0, 0, 568, 320)]; 258 | 259 | // show window 260 | [self.progressWindow makeKeyAndOrderFront:self]; 261 | [self.progressWindow center]; 262 | } 263 | 264 | - (void)emptyTempDirectory 265 | { 266 | // read contents of tmp dir 267 | NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tmpClonePath error:nil]; 268 | 269 | // move tmp dirs to trash 270 | [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation 271 | source:tmpClonePath destination:@"" 272 | files:files tag:nil]; 273 | 274 | } 275 | 276 | 277 | @end 278 | 279 | 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDPluginManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // JDPluginManager.h 3 | // JDPluginManager 4 | // 5 | // Created by Markus Emrich on 03.02.2013. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface JDPluginManager : NSObject 12 | - (void)extendXcodeMenu; 13 | @end 14 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDPluginManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // JDPluginManager.m 3 | // JDPluginManager 4 | // 5 | // Created by Markus Emrich on 03.02.2013. 6 | // 7 | // 8 | 9 | #import "JDPluginManager.h" 10 | #import "JDPluginInstaller.h" 11 | #import "NSURL+JDPluginManager.h" 12 | #import "NSFileManager+JDPluginManager.h" 13 | #import "JDPluginMetaData.h" 14 | #import "global.h" 15 | 16 | 17 | @interface JDPluginManager () 18 | - (void)readAndAddPluginsToMenu:(NSMenu*)menu; 19 | - (void)addPluginNamed:(NSString*)name toMenu:(NSMenu*)menu; 20 | 21 | - (void)showPlugin:(NSMenuItem*)sender; 22 | - (void)updatePlugin:(NSMenuItem*)sender; 23 | - (void)showReadme:(NSMenuItem*)sender; 24 | - (void)showOnGithub:(NSMenuItem*)sender; 25 | - (void)deletePlugin:(NSMenuItem*)sender; 26 | @end 27 | 28 | 29 | @implementation JDPluginManager 30 | 31 | + (void)pluginDidLoad:(NSBundle*)plugin 32 | { 33 | static id sharedPlugin = nil; 34 | static dispatch_once_t onceToken; 35 | dispatch_once(&onceToken, ^{ 36 | sharedPlugin = [[self alloc] init]; 37 | }); 38 | } 39 | 40 | - (id)init 41 | { 42 | self = [super init]; 43 | if (self) { 44 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) 45 | name:NSApplicationDidFinishLaunchingNotification object:nil]; 46 | } 47 | return self; 48 | } 49 | 50 | - (void)applicationDidFinishLaunching:(NSNotification *)notification 51 | { 52 | [self extendXcodeMenu]; 53 | } 54 | 55 | #pragma mark build menu 56 | 57 | - (void)extendXcodeMenu 58 | { 59 | NSMenuItem *pluginsMenuItem = [[NSApp mainMenu] itemWithTitle:JDLocalize(@"keyManagePluginsMenuItemTitle")]; 60 | if (!pluginsMenuItem) { 61 | // find window menu item 62 | NSInteger menuIndex = [[NSApp mainMenu] indexOfItemWithTitle:@"Window"]; 63 | if (menuIndex < 0) { 64 | menuIndex = [[NSApp mainMenu] numberOfItems]; 65 | } 66 | 67 | // insert plugins item 68 | pluginsMenuItem = [[NSApp mainMenu] insertItemWithTitle:JDLocalize(@"keyManagePluginsMenuItemTitle") action:nil keyEquivalent:@"" atIndex:menuIndex]; 69 | pluginsMenuItem.submenu = [[NSMenu alloc] initWithTitle:JDLocalize(@"keyManagePluginsMenuItemTitle")]; 70 | } 71 | 72 | // add menu entries 73 | if (pluginsMenuItem) { 74 | // directory item 75 | NSMenuItem *showDirectoryItem = [[NSMenuItem alloc] initWithTitle:JDLocalize(@"keyShowDirectoryMenuItemTitle") action:@selector(showPluginsDirectory:) keyEquivalent:@""]; 76 | [showDirectoryItem setTarget:self]; 77 | [[pluginsMenuItem submenu] insertItem:showDirectoryItem atIndex:0]; 78 | 79 | // separator 80 | [[pluginsMenuItem submenu] insertItem:[NSMenuItem separatorItem] atIndex:1]; 81 | 82 | // each plugin as subitem 83 | [self readAndAddPluginsToMenu:[pluginsMenuItem submenu]]; 84 | 85 | // separator 86 | [[pluginsMenuItem submenu] addItem:[NSMenuItem separatorItem]]; 87 | 88 | // install item 89 | NSMenuItem *installItem = [[NSMenuItem alloc] initWithTitle:JDLocalize(@"keyInstallMenuItemTitle") action:@selector(installPlugin:) keyEquivalent:@""]; 90 | [installItem setTarget:self]; 91 | [[pluginsMenuItem submenu] addItem:installItem]; 92 | } 93 | } 94 | 95 | - (void)readAndAddPluginsToMenu:(NSMenu*)menu 96 | { 97 | NSArray *contents = [NSFileManager allPluginsWithModifiedDate:NO]; 98 | if (!contents || contents.count == 0) { 99 | // empty item 100 | NSMenuItem *emptyItem = [[NSMenuItem alloc] initWithTitle:JDLocalize(@"keyEmptyMenuItemTitle") action:nil keyEquivalent:@""]; 101 | [emptyItem setEnabled:NO]; 102 | [menu addItem:emptyItem]; 103 | } else { 104 | [contents enumerateObjectsUsingBlock:^(NSString *file, NSUInteger idx, BOOL *stop) { 105 | if ([file hasSuffix:xcodePluginSuffix]) { 106 | // remove suffix 107 | file = [file stringByReplacingOccurrencesOfString:xcodePluginSuffix withString:@""]; 108 | 109 | // plugin item 110 | [self addPluginNamed:file toMenu:menu]; 111 | } 112 | }]; 113 | } 114 | } 115 | 116 | - (void)addPluginNamed:(NSString*)name toMenu:(NSMenu*)menu 117 | { 118 | // plugin item 119 | NSMenuItem *pluginItem = [menu itemWithTitle:name]; 120 | if (!pluginItem) { 121 | pluginItem = [menu addItemWithTitle:name action:@selector(showPlugin:) keyEquivalent:@""]; 122 | pluginItem.submenu = [[NSMenu alloc] init]; 123 | pluginItem.target = self; 124 | } 125 | 126 | // delete item 127 | NSMenuItem *deleteItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat: @"%@…", JDLocalize(@"keyUninstall")] action:@selector(deletePlugin:) keyEquivalent:@""]; 128 | [deleteItem setTarget:self]; 129 | [[pluginItem submenu] addItem:deleteItem]; 130 | 131 | // read meta data 132 | JDPluginMetaData *metaData = [JDPluginMetaData metaDataForPluginAtPath:[[NSURL pluginURLForPluginNamed:name] path]]; 133 | NSString *repositoryPath = [metaData objectForKey:JDPluginManagerMetaDataRepositoryKey]; 134 | NSString *readmePath = [metaData objectForKey:JDPluginManagerMetaDataReadmePathKey]; 135 | 136 | // update item 137 | if (repositoryPath) { 138 | NSMenuItem *updateItem = [[NSMenuItem alloc] initWithTitle:JDLocalize(@"keyUpdateMenuItemTitle") action:@selector(updatePlugin:) keyEquivalent:@""]; 139 | [updateItem setTarget:self]; 140 | [[pluginItem submenu] addItem:updateItem]; 141 | } 142 | 143 | // add separator 144 | [[pluginItem submenu] addItem:[NSMenuItem separatorItem]]; 145 | 146 | // show readme item 147 | if (readmePath && [[NSFileManager defaultManager] fileExistsAtPath:readmePath]) { 148 | NSMenuItem *readmeItem = [[NSMenuItem alloc] initWithTitle:JDLocalize(@"keyShowReadmeMenuItemTitle") action:@selector(showReadme:) keyEquivalent:@""]; 149 | [readmeItem setTarget:self]; 150 | [[pluginItem submenu] addItem:readmeItem]; 151 | } 152 | 153 | // reveal in finder item 154 | NSMenuItem *revealInFinderItem = [[NSMenuItem alloc] initWithTitle:JDLocalize(@"keyRevealInFinderMenuItemTitle") action:@selector(showPlugin:) keyEquivalent:@""]; 155 | [revealInFinderItem setTarget:self]; 156 | [[pluginItem submenu] addItem:revealInFinderItem]; 157 | 158 | // show on github item 159 | if (repositoryPath && [repositoryPath rangeOfString:@"github.com"].length != 0) { 160 | NSMenuItem *githubItem = [[NSMenuItem alloc] initWithTitle:JDLocalize(@"keyShowOnGithubMenuItemTitle") action:@selector(showOnGithub:) keyEquivalent:@""]; 161 | [githubItem setTarget:self]; 162 | [[pluginItem submenu] addItem:githubItem]; 163 | } 164 | } 165 | 166 | #pragma mark actions 167 | 168 | - (void)showPluginsDirectory:(id)sender 169 | { 170 | [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[[NSURL pluginsDirectoryURL]]]; 171 | } 172 | 173 | - (void)showPlugin:(NSMenuItem*)sender 174 | { 175 | NSURL *url; 176 | if ([sender.title isEqualToString:JDLocalize(@"keyRevealInFinderMenuItemTitle")]) { 177 | url = [NSURL pluginURLForPluginNamed:[sender parentItem].title]; 178 | } else { 179 | url = [NSURL pluginURLForPluginNamed:sender.title]; 180 | } 181 | 182 | // open finder 183 | [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[url]]; 184 | } 185 | 186 | - (void)updatePlugin:(NSMenuItem*)sender 187 | { 188 | NSString *pluginName = [sender parentItem].title; 189 | JDPluginMetaData *metaData = [JDPluginMetaData metaDataForPluginAtPath:[[NSURL pluginURLForPluginNamed:pluginName] path]]; 190 | 191 | NSString *gitURL = [metaData objectForKey:JDPluginManagerMetaDataRepositoryKey]; 192 | [[[JDPluginInstaller alloc] init] beginInstallWithRepositoryPath:gitURL searchInSubdirectories:NO]; 193 | } 194 | 195 | - (void)showReadme:(NSMenuItem*)sender 196 | { 197 | NSString *pluginName = [sender parentItem].title; 198 | JDPluginMetaData *metaData = [JDPluginMetaData metaDataForPluginAtPath:[[NSURL pluginURLForPluginNamed:pluginName] path]]; 199 | 200 | NSString *readmePath = [metaData objectForKey:JDPluginManagerMetaDataReadmePathKey]; 201 | [[NSWorkspace sharedWorkspace] openFile:readmePath]; 202 | } 203 | 204 | - (void)showOnGithub:(NSMenuItem*)sender 205 | { 206 | NSString *pluginName = [sender parentItem].title; 207 | JDPluginMetaData *metaData = [JDPluginMetaData metaDataForPluginAtPath:[[NSURL pluginURLForPluginNamed:pluginName] path]]; 208 | 209 | NSString *gitURL = [metaData objectForKey:JDPluginManagerMetaDataRepositoryKey]; 210 | gitURL = [gitURL stringByReplacingOccurrencesOfString:@"git@github.com:" withString:@"github.com/"]; 211 | gitURL = [gitURL stringByReplacingOccurrencesOfString:@".git" withString:@""]; 212 | if (![gitURL hasPrefix:@"HTTP"] || ![gitURL hasPrefix:@"http"]) { 213 | gitURL = [NSString stringWithFormat: @"http://%@", gitURL]; 214 | } 215 | NSURL *url = [NSURL URLWithString:gitURL]; 216 | if (url) { 217 | [[NSWorkspace sharedWorkspace] openURL:url]; 218 | } 219 | } 220 | 221 | - (void)deletePlugin:(NSMenuItem*)sender 222 | { 223 | NSString *pluginName = [sender parentItem].title; 224 | 225 | // construct alert 226 | NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:JDLocalize(@"keyUninstallAlertTitleFormat"), pluginName] 227 | defaultButton:JDLocalize(@"keyUninstall") 228 | alternateButton:JDLocalize(@"keyCancel") 229 | otherButton:nil 230 | informativeTextWithFormat:JDLocalize(@"keyUninstallAlertMessageFormat"), pluginName]; 231 | alert.alertStyle = NSCriticalAlertStyle; 232 | 233 | // show alert 234 | NSInteger selectedButtonIndex = [alert runModal]; 235 | if (selectedButtonIndex == 0) { 236 | return; 237 | } 238 | 239 | // move plugin folder to trash 240 | NSString *pluginPath = [[NSURL pluginURLForPluginNamed:pluginName] path]; 241 | BOOL deleted = [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation 242 | source:[pluginPath stringByDeletingLastPathComponent] 243 | destination:@"" 244 | files:[NSArray arrayWithObject:[pluginPath lastPathComponent]] 245 | tag:nil]; 246 | if (deleted) { 247 | [[sender.parentItem.parentItem submenu] removeItem:sender.parentItem]; 248 | } 249 | } 250 | 251 | - (void)installPlugin:(NSMenuItem*)sender 252 | { 253 | [JDPluginInstaller installPlugin]; 254 | } 255 | 256 | @end 257 | 258 | 259 | 260 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDPluginMetaData.h: -------------------------------------------------------------------------------- 1 | // 2 | // JDPluginMetaData.h 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 09.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern NSString *const JDPluginManagerMetaDataRepositoryKey; 12 | extern NSString *const JDPluginManagerMetaDataReadmePathKey; 13 | 14 | @interface JDPluginMetaData : NSObject { 15 | NSMutableDictionary* _dictionary; 16 | NSString* _pluginPath; 17 | } 18 | 19 | + (JDPluginMetaData*)metaDataForPluginAtPath:(NSString*)pluginPath; 20 | 21 | - (id)initWithPluginPath:(NSString*)pluginPath; 22 | 23 | - (id)objectForKey:(id)aKey; 24 | - (void)setObject:(id)anObject forKey:(id )aKey; 25 | 26 | - (void)findAndSetReadmeAtBuildPath:(NSString*)buildPath; 27 | - (BOOL)save; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDPluginMetaData.m: -------------------------------------------------------------------------------- 1 | // 2 | // JDPluginMetaData.m 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 09.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import "JDPluginMetaData.h" 10 | 11 | NSString *const JDPluginManagerMetaDataFileName = @".JDPluginManager.meta"; 12 | NSString *const JDPluginManagerMetaDataReadmeName = @"README.md"; 13 | 14 | NSString *const JDPluginManagerMetaDataRepositoryKey = @"JDPluginManagerMetaDataRepositoryKey"; 15 | NSString *const JDPluginManagerMetaDataReadmePathKey = @"JDPluginManagerMetaDataReadmePathKey"; 16 | 17 | @interface JDPluginMetaData () 18 | @property (nonatomic, strong) NSMutableDictionary *dictionary; 19 | @property (nonatomic, copy) NSString *pluginPath; 20 | @end 21 | 22 | @implementation JDPluginMetaData 23 | 24 | @synthesize dictionary = _dictionary; 25 | @synthesize pluginPath = _pluginPath; 26 | 27 | + (JDPluginMetaData*)metaDataForPluginAtPath:(NSString*)pluginPath 28 | { 29 | JDPluginMetaData *metaData = [[JDPluginMetaData alloc] initWithPluginPath:pluginPath]; 30 | 31 | // read saved meta data 32 | NSString *path = [pluginPath stringByAppendingPathComponent:JDPluginManagerMetaDataFileName]; 33 | if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { 34 | NSMutableDictionary *dictionary = [[NSDictionary dictionaryWithContentsOfFile:path] mutableCopy]; 35 | if (dictionary) { 36 | metaData.dictionary = dictionary; 37 | } 38 | }; 39 | 40 | // set repo path for JDPluginManager 41 | if([metaData.dictionary objectForKey:JDPluginManagerMetaDataRepositoryKey] == nil && 42 | [pluginPath rangeOfString:@"JDPluginManager"].length > 0) { 43 | [metaData.dictionary setObject:@"git@github.com:jaydee3/JDPluginManager.git" forKey:JDPluginManagerMetaDataRepositoryKey]; 44 | } 45 | 46 | return metaData; 47 | } 48 | 49 | - (id)initWithPluginPath:(NSString*)pluginPath 50 | { 51 | self = [super init]; 52 | if (self) { 53 | _pluginPath = [pluginPath copy]; 54 | _dictionary = [[NSMutableDictionary alloc] init]; 55 | } 56 | return self; 57 | } 58 | 59 | 60 | #pragma mark data 61 | 62 | - (id)objectForKey:(id)aKey { 63 | return [self.dictionary objectForKey:aKey]; 64 | } 65 | 66 | - (void)setObject:(id)anObject forKey:(id )aKey { 67 | [self.dictionary setObject:anObject forKey:aKey]; 68 | } 69 | 70 | - (void)findAndSetReadmeAtBuildPath:(NSString*)buildPath 71 | { 72 | NSString *sourcePath = [buildPath stringByAppendingPathComponent:@"README.md"]; 73 | if (![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) { 74 | sourcePath = [buildPath stringByAppendingPathComponent:@"README"]; 75 | if (![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) { 76 | sourcePath = [buildPath stringByAppendingPathComponent:@"readme"]; 77 | if (![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) { 78 | return; 79 | } 80 | } 81 | } 82 | 83 | NSString *targetPath = [self.pluginPath stringByAppendingPathComponent:JDPluginManagerMetaDataReadmeName]; 84 | [[NSFileManager defaultManager] removeItemAtPath:targetPath error:nil]; 85 | if([[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:targetPath error:nil]) { 86 | [self.dictionary setObject:targetPath forKey:JDPluginManagerMetaDataReadmePathKey]; 87 | [self save]; 88 | } else { 89 | NSLog(@"ERROR copying readme"); 90 | } 91 | } 92 | 93 | - (BOOL)save 94 | { 95 | NSString *path = [self.pluginPath stringByAppendingPathComponent:JDPluginManagerMetaDataFileName]; 96 | BOOL succes = [self.dictionary writeToFile:path atomically:YES]; 97 | 98 | if (!succes) { 99 | NSLog(@"error saving metadata"); 100 | } 101 | 102 | return succes; 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDXcodeBuildTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // JDXcodeBuildTask.h 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 09.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import "NSTaskWithProgress.h" 10 | 11 | extern NSString *const xcodeBuildPath; 12 | 13 | 14 | @class JDInstallProgressWindow; 15 | 16 | @interface JDXcodeBuildTask : NSTaskWithProgress { 17 | NSMutableString* _allXcodeBuildOutput; 18 | NSString* _previousProgressText; 19 | } 20 | 21 | + (instancetype)launchedTaskWithCurrentDirectoryPath:(NSString*)currentDirectory 22 | progressWindow:(JDInstallProgressWindow*)progressWindow 23 | completion:(void(^)())completion; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/JDXcodeBuildTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // JDXcodeBuildTask.m 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 09.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | 10 | #import "global.h" 11 | #import "JDInstallProgressWindow.h" 12 | 13 | #import "JDXcodeBuildTask.h" 14 | 15 | NSString *const xcodeBuildPath = @"/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild"; 16 | 17 | 18 | @interface JDXcodeBuildTask () 19 | @property (atomic, strong) NSMutableString *allXcodeBuildOutput; 20 | @property (atomic, copy) NSString *previousProgressText; 21 | @end 22 | 23 | @implementation JDXcodeBuildTask 24 | 25 | @synthesize allXcodeBuildOutput = _allXcodeBuildOutput; 26 | @synthesize previousProgressText = _previousProgressText; 27 | 28 | + (instancetype)launchedTaskWithCurrentDirectoryPath:(NSString*)currentDirectory 29 | progressWindow:(JDInstallProgressWindow*)progressWindow 30 | completion:(void(^)())completion 31 | { 32 | JDXcodeBuildTask *task = [[JDXcodeBuildTask alloc] initWithCurrentDirectoryPath:currentDirectory 33 | progressWindow:progressWindow 34 | completion:completion]; 35 | [task.task launch]; 36 | return task; 37 | } 38 | 39 | - (id)initWithCurrentDirectoryPath:(NSString*)currentDirectory 40 | progressWindow:(JDInstallProgressWindow*)progressWindow 41 | completion:(void(^)())completion 42 | { 43 | completion = [completion copy]; 44 | [progressWindow appendTitle:JDLocalize(@"keyInstallBuildMessage")]; 45 | 46 | self = [super initWithLaunchPath:xcodeBuildPath arguments:nil currentDirectoryPath:currentDirectory progress:^(NSTask *task, NSString *output) { 47 | [self handleXcodeBuildOutput:output inProgressWindow:progressWindow]; 48 | } completion:^(NSTask *task, NSString *output) { 49 | [self handleXcodeBuildOutput:output inProgressWindow:progressWindow]; 50 | if (completion) { 51 | completion(); 52 | } 53 | }]; 54 | 55 | if (self) { 56 | _allXcodeBuildOutput = [[NSMutableString alloc] init]; 57 | _previousProgressText = [progressWindow.textView.string copy]; 58 | } 59 | 60 | return self; 61 | } 62 | 63 | 64 | #pragma mark handle output 65 | 66 | - (void)handleXcodeBuildOutput:(NSString*)output 67 | inProgressWindow:(JDInstallProgressWindow*)progressWindow 68 | { 69 | [self.allXcodeBuildOutput appendString:output]; 70 | 71 | progressWindow.textView.string = [NSString stringWithFormat: @"%@%@\n", 72 | self.previousProgressText, 73 | [self minimizedXcodeBuildOutput]]; 74 | [progressWindow scrollToBottom]; 75 | } 76 | 77 | - (NSString*)minimizedXcodeBuildOutput 78 | { 79 | NSMutableString *minimizedOutput = [NSMutableString string]; 80 | NSArray *allLines = [self.allXcodeBuildOutput componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; 81 | for (NSString *line in allLines) { 82 | for (NSString *search in @[@"BUILD", @"ERROR", @"error"]) { 83 | if ([line rangeOfString:search].length > 0) { 84 | [minimizedOutput appendFormat:@"\n%@\n", line]; 85 | } 86 | } 87 | } 88 | return [minimizedOutput stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/NSFileManager+JDPluginManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSFileManager+JDPluginManager.h 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 09.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern NSString const* JDPluginNameKey; 12 | extern NSString const* JDPluginModifiedDate; 13 | 14 | 15 | @interface NSFileManager (JDPluginManager) 16 | 17 | + (NSArray*)allPluginsWithModifiedDate:(BOOL)withModifiedDate; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/NSFileManager+JDPluginManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSFileManager+JDPluginManager.m 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 09.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import "NSURL+JDPluginManager.h" 10 | 11 | #import "NSFileManager+JDPluginManager.h" 12 | 13 | NSString const* JDPluginNameKey = @"JDPluginNameKey"; 14 | NSString const* JDPluginModifiedDate = @"JDPluginModifiedDate"; 15 | 16 | 17 | @implementation NSFileManager (JDPluginManager) 18 | 19 | + (NSArray*)allPluginsWithModifiedDate:(BOOL)withModifiedDate 20 | { 21 | NSError* error = nil; 22 | NSArray* filesArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[[NSURL pluginsDirectoryURL] path] error:&error]; 23 | if (!filesArray) { 24 | NSLog(@"error getting plugin files at path %@: %@", [[NSURL pluginsDirectoryURL] path], error); 25 | return nil; 26 | } 27 | 28 | // order by name 29 | filesArray = [filesArray sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) { 30 | return [obj1 compare:obj2]; 31 | }]; 32 | 33 | // return only files 34 | if (!withModifiedDate) { 35 | return filesArray; 36 | } 37 | 38 | // read modified date 39 | NSMutableArray* filesAndProperties = [NSMutableArray arrayWithCapacity:[filesArray count]]; 40 | for (NSString* file in filesArray) { 41 | NSString* filePath = [[[NSURL pluginsDirectoryURL] path] stringByAppendingPathComponent:file]; 42 | NSDictionary* properties = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error]; 43 | NSDate* modDate = [properties objectForKey:NSFileModificationDate]; 44 | 45 | if(error != nil) { 46 | NSLog(@"error reading modified date for file %@", file); 47 | continue; 48 | } 49 | 50 | [filesAndProperties addObject:[NSDictionary dictionaryWithObjectsAndKeys: 51 | file, JDPluginNameKey, 52 | modDate, JDPluginModifiedDate, 53 | nil]]; 54 | } 55 | 56 | return filesAndProperties; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/NSTaskWithProgress.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSTaskWithProgress.h 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 08.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void(^NSTaskWithOutputProgressBlock)(NSTask* task, NSString* output); 12 | 13 | @interface NSTaskWithProgress : NSObject { 14 | NSTask* _task; 15 | NSTaskWithOutputProgressBlock _progressBlock; 16 | } 17 | 18 | @property (nonatomic, readonly) NSTask *task; 19 | 20 | + (NSTaskWithProgress*)launchedTaskWithLaunchPath:(NSString*)launchPath 21 | arguments:(NSArray*)arguments 22 | progress:(NSTaskWithOutputProgressBlock)progressBlock 23 | completion:(NSTaskWithOutputProgressBlock)completionBlock; 24 | 25 | + (NSTaskWithProgress*)launchedTaskWithLaunchPath:(NSString*)launchPath 26 | arguments:(NSArray*)arguments 27 | currentDirectoryPath:(NSString*)currentDirectoryPath 28 | progress:(NSTaskWithOutputProgressBlock)progressBlock 29 | completion:(NSTaskWithOutputProgressBlock)completionBlock; 30 | 31 | 32 | - (id)initWithLaunchPath:(NSString*)launchPath 33 | arguments:(NSArray*)arguments 34 | currentDirectoryPath:(NSString*)currentDirectoryPath 35 | progress:(NSTaskWithOutputProgressBlock)progressBlock 36 | completion:(NSTaskWithOutputProgressBlock)completionBlock; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/NSTaskWithProgress.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSTaskWithProgress.m 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 08.03.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import "NSTaskWithProgress.h" 10 | 11 | 12 | @interface NSTaskWithProgress () 13 | @property (nonatomic, strong) NSTask *task; 14 | @property (nonatomic, copy) NSTaskWithOutputProgressBlock progressBlock; 15 | @end 16 | 17 | @implementation NSTaskWithProgress 18 | 19 | @synthesize task = _task; 20 | @synthesize progressBlock = _progressBlock; 21 | 22 | + (instancetype)launchedTaskWithLaunchPath:(NSString*)launchPath 23 | arguments:(NSArray*)arguments 24 | progress:(NSTaskWithOutputProgressBlock)progressBlock 25 | completion:(NSTaskWithOutputProgressBlock)completionBlock 26 | { 27 | return [self launchedTaskWithLaunchPath:launchPath 28 | arguments:arguments 29 | currentDirectoryPath:nil 30 | progress:progressBlock 31 | completion:completionBlock]; 32 | } 33 | 34 | + (instancetype)launchedTaskWithLaunchPath:(NSString*)launchPath 35 | arguments:(NSArray*)arguments 36 | currentDirectoryPath:(NSString*)currentDirectoryPath 37 | progress:(NSTaskWithOutputProgressBlock)progressBlock 38 | completion:(NSTaskWithOutputProgressBlock)completionBlock 39 | { 40 | NSTaskWithProgress* task = [[self alloc] initWithLaunchPath:launchPath 41 | arguments:arguments 42 | currentDirectoryPath:currentDirectoryPath 43 | progress:progressBlock 44 | completion:completionBlock]; 45 | // launch task 46 | [task.task launch]; 47 | 48 | return task; 49 | } 50 | 51 | #pragma mark NSTaskWithProgress 52 | 53 | - (id)initWithLaunchPath:(NSString*)launchPath 54 | arguments:(NSArray*)arguments 55 | currentDirectoryPath:(NSString*)currentDirectoryPath 56 | progress:(NSTaskWithOutputProgressBlock)progressBlock 57 | completion:(NSTaskWithOutputProgressBlock)completionBlock 58 | { 59 | self = [super init]; 60 | if (self) { 61 | NSParameterAssert(launchPath); 62 | 63 | // setup task 64 | NSTask *task = [[NSTask alloc] init]; 65 | [task setStandardOutput:[NSPipe pipe]]; 66 | [task setStandardError:task.standardOutput]; 67 | [task setLaunchPath:launchPath]; 68 | self.task = task; 69 | 70 | // set arguments, if available 71 | if (arguments != nil && arguments.count > 0) { 72 | [task setArguments:arguments]; 73 | } 74 | 75 | // set current dir, if available 76 | if (currentDirectoryPath != nil) { 77 | [task setCurrentDirectoryPath:currentDirectoryPath]; 78 | } 79 | 80 | // setup constant updates 81 | self.progressBlock = progressBlock; 82 | NSFileHandle* fileHandle = [[task standardOutput] fileHandleForReading]; 83 | [[NSNotificationCenter defaultCenter] addObserver:self 84 | selector:@selector(readPipe:) 85 | name:NSFileHandleReadCompletionNotification 86 | object:fileHandle]; 87 | [fileHandle readInBackgroundAndNotify]; 88 | 89 | // setup completion block 90 | [task setTerminationHandler:^(NSTask* task) 91 | { 92 | // unregister from updates 93 | [[NSNotificationCenter defaultCenter] removeObserver:self 94 | name:NSFileHandleReadCompletionNotification 95 | object:fileHandle]; 96 | 97 | // catch output 98 | NSPipe* outPipe = task.standardOutput; 99 | NSData* output = [[outPipe fileHandleForReading] readDataToEndOfFile]; 100 | NSString* outputString = [[NSString alloc] initWithData:output encoding:NSUTF8StringEncoding]; 101 | 102 | // call completion block 103 | if (completionBlock) { 104 | dispatch_async(dispatch_get_main_queue(), ^{ 105 | completionBlock(task,outputString); 106 | }); 107 | } 108 | }]; 109 | } 110 | return self; 111 | } 112 | 113 | 114 | - (void)readPipe:(NSNotification*)notification 115 | { 116 | if (self.progressBlock) { 117 | NSData *data = [notification.userInfo objectForKey:NSFileHandleNotificationDataItem]; 118 | NSString *readData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 119 | dispatch_async(dispatch_get_main_queue(), ^{ 120 | self.progressBlock(self.task,readData); 121 | }); 122 | } 123 | 124 | // stay reading 125 | [[[self.task standardOutput] fileHandleForReading] readInBackgroundAndNotify]; 126 | } 127 | 128 | @end 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/NSURL+JDPluginManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+JDPluginManager.h 3 | // JDPluginManager 4 | // 5 | // Created by Markus Emrich on 05.02.13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | extern NSString *const xcodePluginSuffix; 12 | 13 | @interface NSURL (JDPluginManager) 14 | 15 | + (NSURL*)pluginURLForPluginNamed:(NSString*)pluginName; 16 | + (NSURL*)pluginsDirectoryURL; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /JDPluginManager/Classes/NSURL+JDPluginManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+JDPluginManager.m 3 | // JDPluginManager 4 | // 5 | // Created by Markus Emrich on 05.02.13. 6 | // 7 | // 8 | 9 | #import "NSURL+JDPluginManager.h" 10 | 11 | NSString *const pluginsDirectoryPath = @"~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/"; 12 | NSString *const xcodePluginSuffix = @".xcplugin"; 13 | 14 | @implementation NSURL (JDPluginManager) 15 | 16 | + (NSURL*)pluginURLForPluginNamed:(NSString*)pluginName 17 | { 18 | if (![pluginName hasSuffix:xcodePluginSuffix]) { 19 | pluginName = [pluginName stringByAppendingString:xcodePluginSuffix]; 20 | } 21 | 22 | return [[self pluginsDirectoryURL] URLByAppendingPathComponent:pluginName isDirectory:YES]; 23 | } 24 | 25 | + (NSURL*)pluginsDirectoryURL 26 | { 27 | return [NSURL fileURLWithPath:[pluginsDirectoryPath stringByExpandingTildeInPath] isDirectory:YES]; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /JDPluginManager/ExampleProject/ExampleProject.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D246418916C04B21005DB12C /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D246418816C04B21005DB12C /* Cocoa.framework */; }; 11 | D2765EE416EBDA360095A9C9 /* JDGitCloneTask.m in Sources */ = {isa = PBXBuildFile; fileRef = D2765ED516EBDA360095A9C9 /* JDGitCloneTask.m */; }; 12 | D2765EE516EBDA360095A9C9 /* JDInstallProgressWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = D2765ED716EBDA360095A9C9 /* JDInstallProgressWindow.m */; }; 13 | D2765EE616EBDA360095A9C9 /* JDPluginInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = D2765ED916EBDA360095A9C9 /* JDPluginInstaller.m */; }; 14 | D2765EE716EBDA360095A9C9 /* JDPluginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D2765EDB16EBDA360095A9C9 /* JDPluginManager.m */; }; 15 | D2765EE816EBDA360095A9C9 /* JDPluginMetaData.m in Sources */ = {isa = PBXBuildFile; fileRef = D2765EDD16EBDA360095A9C9 /* JDPluginMetaData.m */; }; 16 | D2765EE916EBDA360095A9C9 /* JDXcodeBuildTask.m in Sources */ = {isa = PBXBuildFile; fileRef = D2765EDF16EBDA360095A9C9 /* JDXcodeBuildTask.m */; }; 17 | D2765EEA16EBDA360095A9C9 /* NSTaskWithProgress.m in Sources */ = {isa = PBXBuildFile; fileRef = D2765EE116EBDA360095A9C9 /* NSTaskWithProgress.m */; }; 18 | D2765EEB16EBDA360095A9C9 /* NSURL+JDPluginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D2765EE316EBDA360095A9C9 /* NSURL+JDPluginManager.m */; }; 19 | D2765EEE16EBDA470095A9C9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2765EED16EBDA470095A9C9 /* Localizable.strings */; }; 20 | D2765EF616EBED830095A9C9 /* NSFileManager+JDPluginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D2765EF516EBED830095A9C9 /* NSFileManager+JDPluginManager.m */; }; 21 | D2E2D32616D4304B00447430 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D2E2D32116D4304B00447430 /* main.m */; }; 22 | D2E2D32716D4304B00447430 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D2E2D32216D4304B00447430 /* MainMenu.xib */; }; 23 | D2E2D32816D4304B00447430 /* TAAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D2E2D32416D4304B00447430 /* TAAppDelegate.m */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | D246418516C04B21005DB12C /* ExampleProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | D246418816C04B21005DB12C /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 29 | D246418B16C04B21005DB12C /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 30 | D246418C16C04B21005DB12C /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 31 | D246418D16C04B21005DB12C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 32 | D2765ED416EBDA360095A9C9 /* JDGitCloneTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDGitCloneTask.h; sourceTree = ""; }; 33 | D2765ED516EBDA360095A9C9 /* JDGitCloneTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDGitCloneTask.m; sourceTree = ""; }; 34 | D2765ED616EBDA360095A9C9 /* JDInstallProgressWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDInstallProgressWindow.h; sourceTree = ""; }; 35 | D2765ED716EBDA360095A9C9 /* JDInstallProgressWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDInstallProgressWindow.m; sourceTree = ""; }; 36 | D2765ED816EBDA360095A9C9 /* JDPluginInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDPluginInstaller.h; sourceTree = ""; }; 37 | D2765ED916EBDA360095A9C9 /* JDPluginInstaller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDPluginInstaller.m; sourceTree = ""; }; 38 | D2765EDA16EBDA360095A9C9 /* JDPluginManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDPluginManager.h; sourceTree = ""; }; 39 | D2765EDB16EBDA360095A9C9 /* JDPluginManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDPluginManager.m; sourceTree = ""; }; 40 | D2765EDC16EBDA360095A9C9 /* JDPluginMetaData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDPluginMetaData.h; sourceTree = ""; }; 41 | D2765EDD16EBDA360095A9C9 /* JDPluginMetaData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDPluginMetaData.m; sourceTree = ""; }; 42 | D2765EDE16EBDA360095A9C9 /* JDXcodeBuildTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JDXcodeBuildTask.h; sourceTree = ""; }; 43 | D2765EDF16EBDA360095A9C9 /* JDXcodeBuildTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JDXcodeBuildTask.m; sourceTree = ""; }; 44 | D2765EE016EBDA360095A9C9 /* NSTaskWithProgress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSTaskWithProgress.h; sourceTree = ""; }; 45 | D2765EE116EBDA360095A9C9 /* NSTaskWithProgress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSTaskWithProgress.m; sourceTree = ""; }; 46 | D2765EE216EBDA360095A9C9 /* NSURL+JDPluginManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+JDPluginManager.h"; sourceTree = ""; }; 47 | D2765EE316EBDA360095A9C9 /* NSURL+JDPluginManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+JDPluginManager.m"; sourceTree = ""; }; 48 | D2765EEC16EBDA470095A9C9 /* global.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = global.h; path = ../global.h; sourceTree = ""; }; 49 | D2765EED16EBDA470095A9C9 /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = ../Localizable.strings; sourceTree = ""; }; 50 | D2765EF416EBED830095A9C9 /* NSFileManager+JDPluginManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFileManager+JDPluginManager.h"; sourceTree = ""; }; 51 | D2765EF516EBED830095A9C9 /* NSFileManager+JDPluginManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager+JDPluginManager.m"; sourceTree = ""; }; 52 | D2E2D31F16D4304B00447430 /* ExampleProject-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "ExampleProject-Info.plist"; sourceTree = ""; }; 53 | D2E2D32016D4304B00447430 /* ExampleProject-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ExampleProject-Prefix.pch"; sourceTree = ""; }; 54 | D2E2D32116D4304B00447430 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 55 | D2E2D32216D4304B00447430 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; 56 | D2E2D32316D4304B00447430 /* TAAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TAAppDelegate.h; sourceTree = ""; }; 57 | D2E2D32416D4304B00447430 /* TAAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TAAppDelegate.m; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | D246418216C04B21005DB12C /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | D246418916C04B21005DB12C /* Cocoa.framework in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | D246417C16C04B21005DB12C = { 73 | isa = PBXGroup; 74 | children = ( 75 | D2E2D31E16D4304B00447430 /* ExampleProject */, 76 | D246418716C04B21005DB12C /* Frameworks */, 77 | D246418616C04B21005DB12C /* Products */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | D246418616C04B21005DB12C /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | D246418516C04B21005DB12C /* ExampleProject.app */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | D246418716C04B21005DB12C /* Frameworks */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | D246418816C04B21005DB12C /* Cocoa.framework */, 93 | D246418A16C04B21005DB12C /* Other Frameworks */, 94 | ); 95 | name = Frameworks; 96 | sourceTree = ""; 97 | }; 98 | D246418A16C04B21005DB12C /* Other Frameworks */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | D246418B16C04B21005DB12C /* AppKit.framework */, 102 | D246418C16C04B21005DB12C /* CoreData.framework */, 103 | D246418D16C04B21005DB12C /* Foundation.framework */, 104 | ); 105 | name = "Other Frameworks"; 106 | sourceTree = ""; 107 | }; 108 | D2765ED316EBDA360095A9C9 /* Plugin */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | D2765EEF16EBDA4B0095A9C9 /* Localization */, 112 | D2765EF016EBDA570095A9C9 /* Actual Plugin */, 113 | D2765EF116EBDA6B0095A9C9 /* UI */, 114 | D2765EF216EBDA820095A9C9 /* Tasks */, 115 | D2765EF316EBDA8C0095A9C9 /* Helper */, 116 | ); 117 | name = Plugin; 118 | path = ../../Classes; 119 | sourceTree = ""; 120 | }; 121 | D2765EEF16EBDA4B0095A9C9 /* Localization */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | D2765EEC16EBDA470095A9C9 /* global.h */, 125 | D2765EED16EBDA470095A9C9 /* Localizable.strings */, 126 | ); 127 | name = Localization; 128 | sourceTree = ""; 129 | }; 130 | D2765EF016EBDA570095A9C9 /* Actual Plugin */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | D2765ED816EBDA360095A9C9 /* JDPluginInstaller.h */, 134 | D2765ED916EBDA360095A9C9 /* JDPluginInstaller.m */, 135 | D2765EDA16EBDA360095A9C9 /* JDPluginManager.h */, 136 | D2765EDB16EBDA360095A9C9 /* JDPluginManager.m */, 137 | D2765EDC16EBDA360095A9C9 /* JDPluginMetaData.h */, 138 | D2765EDD16EBDA360095A9C9 /* JDPluginMetaData.m */, 139 | ); 140 | name = "Actual Plugin"; 141 | sourceTree = ""; 142 | }; 143 | D2765EF116EBDA6B0095A9C9 /* UI */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | D2765ED616EBDA360095A9C9 /* JDInstallProgressWindow.h */, 147 | D2765ED716EBDA360095A9C9 /* JDInstallProgressWindow.m */, 148 | ); 149 | name = UI; 150 | sourceTree = ""; 151 | }; 152 | D2765EF216EBDA820095A9C9 /* Tasks */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | D2765EE016EBDA360095A9C9 /* NSTaskWithProgress.h */, 156 | D2765EE116EBDA360095A9C9 /* NSTaskWithProgress.m */, 157 | D2765ED416EBDA360095A9C9 /* JDGitCloneTask.h */, 158 | D2765ED516EBDA360095A9C9 /* JDGitCloneTask.m */, 159 | D2765EDE16EBDA360095A9C9 /* JDXcodeBuildTask.h */, 160 | D2765EDF16EBDA360095A9C9 /* JDXcodeBuildTask.m */, 161 | ); 162 | name = Tasks; 163 | sourceTree = ""; 164 | }; 165 | D2765EF316EBDA8C0095A9C9 /* Helper */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | D2765EE216EBDA360095A9C9 /* NSURL+JDPluginManager.h */, 169 | D2765EE316EBDA360095A9C9 /* NSURL+JDPluginManager.m */, 170 | D2765EF416EBED830095A9C9 /* NSFileManager+JDPluginManager.h */, 171 | D2765EF516EBED830095A9C9 /* NSFileManager+JDPluginManager.m */, 172 | ); 173 | name = Helper; 174 | sourceTree = ""; 175 | }; 176 | D2E2D31E16D4304B00447430 /* ExampleProject */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | D2E2D32116D4304B00447430 /* main.m */, 180 | D2E2D32316D4304B00447430 /* TAAppDelegate.h */, 181 | D2E2D32416D4304B00447430 /* TAAppDelegate.m */, 182 | D2765ED316EBDA360095A9C9 /* Plugin */, 183 | D2E2D32916D4305500447430 /* Resources */, 184 | ); 185 | path = ExampleProject; 186 | sourceTree = ""; 187 | }; 188 | D2E2D32916D4305500447430 /* Resources */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | D2E2D31F16D4304B00447430 /* ExampleProject-Info.plist */, 192 | D2E2D32016D4304B00447430 /* ExampleProject-Prefix.pch */, 193 | D2E2D32216D4304B00447430 /* MainMenu.xib */, 194 | ); 195 | name = Resources; 196 | sourceTree = ""; 197 | }; 198 | /* End PBXGroup section */ 199 | 200 | /* Begin PBXNativeTarget section */ 201 | D246418416C04B21005DB12C /* ExampleProject */ = { 202 | isa = PBXNativeTarget; 203 | buildConfigurationList = D24641A216C04B21005DB12C /* Build configuration list for PBXNativeTarget "ExampleProject" */; 204 | buildPhases = ( 205 | D246418116C04B21005DB12C /* Sources */, 206 | D246418216C04B21005DB12C /* Frameworks */, 207 | D246418316C04B21005DB12C /* Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | ); 213 | name = ExampleProject; 214 | productName = ListInstalledPluginsTest; 215 | productReference = D246418516C04B21005DB12C /* ExampleProject.app */; 216 | productType = "com.apple.product-type.application"; 217 | }; 218 | /* End PBXNativeTarget section */ 219 | 220 | /* Begin PBXProject section */ 221 | D246417D16C04B21005DB12C /* Project object */ = { 222 | isa = PBXProject; 223 | attributes = { 224 | CLASSPREFIX = TA; 225 | LastUpgradeCheck = 0510; 226 | ORGANIZATIONNAME = nxtbgthng; 227 | }; 228 | buildConfigurationList = D246418016C04B21005DB12C /* Build configuration list for PBXProject "ExampleProject" */; 229 | compatibilityVersion = "Xcode 3.2"; 230 | developmentRegion = English; 231 | hasScannedForEncodings = 0; 232 | knownRegions = ( 233 | en, 234 | ); 235 | mainGroup = D246417C16C04B21005DB12C; 236 | productRefGroup = D246418616C04B21005DB12C /* Products */; 237 | projectDirPath = ""; 238 | projectRoot = ""; 239 | targets = ( 240 | D246418416C04B21005DB12C /* ExampleProject */, 241 | ); 242 | }; 243 | /* End PBXProject section */ 244 | 245 | /* Begin PBXResourcesBuildPhase section */ 246 | D246418316C04B21005DB12C /* Resources */ = { 247 | isa = PBXResourcesBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | D2E2D32716D4304B00447430 /* MainMenu.xib in Resources */, 251 | D2765EEE16EBDA470095A9C9 /* Localizable.strings in Resources */, 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | /* End PBXResourcesBuildPhase section */ 256 | 257 | /* Begin PBXSourcesBuildPhase section */ 258 | D246418116C04B21005DB12C /* Sources */ = { 259 | isa = PBXSourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | D2E2D32616D4304B00447430 /* main.m in Sources */, 263 | D2E2D32816D4304B00447430 /* TAAppDelegate.m in Sources */, 264 | D2765EE416EBDA360095A9C9 /* JDGitCloneTask.m in Sources */, 265 | D2765EE516EBDA360095A9C9 /* JDInstallProgressWindow.m in Sources */, 266 | D2765EE616EBDA360095A9C9 /* JDPluginInstaller.m in Sources */, 267 | D2765EE716EBDA360095A9C9 /* JDPluginManager.m in Sources */, 268 | D2765EE816EBDA360095A9C9 /* JDPluginMetaData.m in Sources */, 269 | D2765EE916EBDA360095A9C9 /* JDXcodeBuildTask.m in Sources */, 270 | D2765EEA16EBDA360095A9C9 /* NSTaskWithProgress.m in Sources */, 271 | D2765EEB16EBDA360095A9C9 /* NSURL+JDPluginManager.m in Sources */, 272 | D2765EF616EBED830095A9C9 /* NSFileManager+JDPluginManager.m in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXSourcesBuildPhase section */ 277 | 278 | /* Begin XCBuildConfiguration section */ 279 | D24641A016C04B21005DB12C /* Debug */ = { 280 | isa = XCBuildConfiguration; 281 | buildSettings = { 282 | ALWAYS_SEARCH_USER_PATHS = NO; 283 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 284 | CLANG_CXX_LIBRARY = "libc++"; 285 | CLANG_WARN_CONSTANT_CONVERSION = YES; 286 | CLANG_WARN_EMPTY_BODY = YES; 287 | CLANG_WARN_ENUM_CONVERSION = YES; 288 | CLANG_WARN_INT_CONVERSION = YES; 289 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 290 | COPY_PHASE_STRIP = NO; 291 | GCC_C_LANGUAGE_STANDARD = gnu99; 292 | GCC_DYNAMIC_NO_PIC = NO; 293 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 294 | GCC_OPTIMIZATION_LEVEL = 0; 295 | GCC_PREPROCESSOR_DEFINITIONS = ( 296 | "DEBUG=1", 297 | "$(inherited)", 298 | ); 299 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 302 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 303 | GCC_WARN_UNUSED_VARIABLE = YES; 304 | MACOSX_DEPLOYMENT_TARGET = 10.8; 305 | ONLY_ACTIVE_ARCH = YES; 306 | SDKROOT = macosx; 307 | }; 308 | name = Debug; 309 | }; 310 | D24641A116C04B21005DB12C /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ALWAYS_SEARCH_USER_PATHS = NO; 314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 315 | CLANG_CXX_LIBRARY = "libc++"; 316 | CLANG_WARN_CONSTANT_CONVERSION = YES; 317 | CLANG_WARN_EMPTY_BODY = YES; 318 | CLANG_WARN_ENUM_CONVERSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 321 | COPY_PHASE_STRIP = YES; 322 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 323 | GCC_C_LANGUAGE_STANDARD = gnu99; 324 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 325 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 326 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 327 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 328 | GCC_WARN_UNUSED_VARIABLE = YES; 329 | MACOSX_DEPLOYMENT_TARGET = 10.8; 330 | SDKROOT = macosx; 331 | }; 332 | name = Release; 333 | }; 334 | D24641A316C04B21005DB12C /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | COMBINE_HIDPI_IMAGES = YES; 338 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 339 | GCC_PREFIX_HEADER = "ExampleProject/ExampleProject-Prefix.pch"; 340 | GCC_PREPROCESSOR_DEFINITIONS = ( 341 | "DEBUG=1", 342 | "$(inherited)", 343 | "JDPluginManagerTest=1", 344 | ); 345 | INFOPLIST_FILE = "ExampleProject/ExampleProject-Info.plist"; 346 | PRODUCT_NAME = ExampleProject; 347 | WRAPPER_EXTENSION = app; 348 | }; 349 | name = Debug; 350 | }; 351 | D24641A416C04B21005DB12C /* Release */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | COMBINE_HIDPI_IMAGES = YES; 355 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 356 | GCC_PREFIX_HEADER = "ExampleProject/ExampleProject-Prefix.pch"; 357 | INFOPLIST_FILE = "ExampleProject/ExampleProject-Info.plist"; 358 | PRODUCT_NAME = ExampleProject; 359 | WRAPPER_EXTENSION = app; 360 | }; 361 | name = Release; 362 | }; 363 | /* End XCBuildConfiguration section */ 364 | 365 | /* Begin XCConfigurationList section */ 366 | D246418016C04B21005DB12C /* Build configuration list for PBXProject "ExampleProject" */ = { 367 | isa = XCConfigurationList; 368 | buildConfigurations = ( 369 | D24641A016C04B21005DB12C /* Debug */, 370 | D24641A116C04B21005DB12C /* Release */, 371 | ); 372 | defaultConfigurationIsVisible = 0; 373 | defaultConfigurationName = Release; 374 | }; 375 | D24641A216C04B21005DB12C /* Build configuration list for PBXNativeTarget "ExampleProject" */ = { 376 | isa = XCConfigurationList; 377 | buildConfigurations = ( 378 | D24641A316C04B21005DB12C /* Debug */, 379 | D24641A416C04B21005DB12C /* Release */, 380 | ); 381 | defaultConfigurationIsVisible = 0; 382 | defaultConfigurationName = Release; 383 | }; 384 | /* End XCConfigurationList section */ 385 | }; 386 | rootObject = D246417D16C04B21005DB12C /* Project object */; 387 | } 388 | -------------------------------------------------------------------------------- /JDPluginManager/ExampleProject/ExampleProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JDPluginManager/ExampleProject/ExampleProject/ExampleProject-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.nxtbgthng.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2013 nxtbgthng. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /JDPluginManager/ExampleProject/ExampleProject/ExampleProject-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'ExampleProject' target in the 'ExampleProject' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /JDPluginManager/ExampleProject/ExampleProject/TAAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // TAAppDelegate.h 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 04.02.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class JDPluginManager; 12 | 13 | @interface TAAppDelegate : NSObject 14 | 15 | @property (nonatomic, strong) JDPluginManager *manager; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /JDPluginManager/ExampleProject/ExampleProject/TAAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // TAAppDelegate.m 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 04.02.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import "TAAppDelegate.h" 10 | 11 | #import "JDPluginManager.h" 12 | #import "JDPluginInstaller.h" 13 | 14 | @implementation TAAppDelegate 15 | 16 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 17 | { 18 | self.manager = [[[JDPluginManager alloc] init] autorelease]; 19 | 20 | // extend menu 21 | [self.manager extendXcodeMenu]; 22 | 23 | // install a new plugin 24 | [JDPluginInstaller installPlugin]; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /JDPluginManager/ExampleProject/ExampleProject/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ExampleProject 4 | // 5 | // Created by Markus Emrich on 04.02.13. 6 | // Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **)argv); 14 | } 15 | -------------------------------------------------------------------------------- /JDPluginManager/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.nxtbgthng.${PRODUCT_NAME:identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | ${CURRENT_PROJECT_VERSION} 19 | CFBundleVersion 20 | ${CURRENT_PROJECT_VERSION} 21 | DVTPlugInCompatibilityUUIDs 22 | 23 | A2E4D43F-41F4-4FB9-BB94-7177011C9AED 24 | 63FC1C47-140D-42B0-BB4D-A10B2D225574 25 | 37B30044-3B14-46BA-ABAA-F01000C27B63 26 | C4A681B0-4A26-480E-93EC-1218098B9AA0 27 | 28 | NSPrincipalClass 29 | JDPluginManager 30 | XC4Compatible 31 | 32 | XCGCReady 33 | 34 | XCPluginHasUI 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /JDPluginManager/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizables.strings 3 | ExampleProject 4 | 5 | Created by Markus Emrich on 04.02.13. 6 | Copyright (c) 2013 nxtbgthng. All rights reserved. 7 | */ 8 | 9 | 10 | // general 11 | "keyOK" = "OK"; 12 | "keyCancel" = "Cancel"; 13 | 14 | // menu items 15 | "keyManagePluginsMenuItemTitle" = "Plugins"; 16 | "keyEmptyMenuItemTitle" = "- No installed Plugins -"; 17 | "keyShowDirectoryMenuItemTitle" = "Reveal plugin directory"; 18 | "keyInstallMenuItemTitle" = "Install a new plugin…"; 19 | 20 | "keyUpdateMenuItemTitle" = "Update…"; 21 | "keyShowReadmeMenuItemTitle" = "Open Readme"; 22 | "keyRevealInFinderMenuItemTitle" = "Reveal in Finder"; 23 | "keyShowOnGithubMenuItemTitle" = "Show on Github.com"; 24 | 25 | // uninstalling 26 | "keyUninstall" = "Uninstall"; 27 | "keyUninstallAlertTitleFormat" = "Uninstall Plugin '%@'?"; 28 | "keyUninstallAlertMessageFormat" = "This will move the plugin '%@' to your trash. \n\nIt won't be loaded after the next start of Xcode."; 29 | 30 | // installing 31 | "keyInstall" = "Install"; 32 | "keyInstallAlertTitleFormat" = "Enter plugin repository URL"; 33 | "keyInstallAlertMessage" = "The URL must be a git repository. The project contents (master branch) must contain a buildable Xcode plugin project."; 34 | "keyInstallAlertExampleText" = "git@github.com:jaydee3/JDPluginManager.git"; 35 | "keyInstallAlertCheckboxText" = "Search for plugins in subdirectories (Ignoring the top level project file.)"; 36 | "keyInstallProgressTitle" = "Installing plugin…"; 37 | "keyInstallCloneMessage" = "Cloning repository now…"; 38 | "keyInstallBuildMessage" = "Building the plugin now…"; 39 | 40 | "keyInstallFailureMessage" = "ERROR: No new/updated plugin installed."; 41 | "keyInstallSuccessMessageFormat" = "SUCCESS: Installed/updated %@ successfully.\n Restart Xcode to use the plugin."; 42 | "keyInstallTrashingTmpDirectory" = "Moving cloned repository into trash now…"; 43 | 44 | // errors 45 | "keyInstallErrorTitle" = "Error during installation"; 46 | "keyInstallErrorMessage" = "Could not install plugin. If the clone was successful, the project is now in your trash folder."; 47 | 48 | "keyErrorXcodeToolsMissingTitle" = "xcodebuild/xcodeproj not found"; 49 | "keyErrorXcodeToolsMissingMessage" = "Xcode must be in installed in /Applications. Also install the Command Line Tools from the Xcode Settings, if you haven't already."; 50 | 51 | "keyErrorGitMissingTitle" = "git not found"; 52 | "keyErrorGitMissingMessage" = "You need to have git installed (/usr/bin/git) in order to install plugins. Please install git."; 53 | 54 | -------------------------------------------------------------------------------- /JDPluginManager/global.h: -------------------------------------------------------------------------------- 1 | // 2 | // global.h 3 | // JDPluginManager 4 | // 5 | // Created by Markus Emrich on 04.02.13. 6 | // 7 | // 8 | 9 | #if JDPluginManagerTest == 1 10 | #define JDLocalize(keyName) NSLocalizedString(keyName, nil) 11 | #else 12 | #define JDLocalize(keyName) NSLocalizedStringFromTableInBundle(keyName, @"Localizable", [NSBundle bundleForClass:[self class]], nil) 13 | #endif 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JDPluginManager 2 | 3 | ## Overview 4 | 5 | Are you using a lot of XCode Plugins? 6 | This plugin makes it easy to install, update and remove them. It adds a new MenuItem `Plugins` in the Menu Bar of Xcode, as you can see in the screenshots. 7 | 8 | **Key Features:** 9 | 10 | - Install plugins easily by just entering a git repository url 11 | - One-click updates (for plugins installed with JDPluginManager) 12 | - Uninstall plugins 13 | 14 | **Screenshots:** 15 | 16 | ![Screenshot](assets/screenshot1.png "Menu Screenshot") 17 | ![Screenshot](assets/screenshot2.png "Uninstall Screenshot") 18 | ![Screenshot](assets/screenshot3.png "Installation Screenshot") 19 | 20 | ## Installation 21 | 22 | Simply build this Xcode project once and restart Xcode. You can delete the project afterwards. (The plugin will be copied to your system automatically after the build.) 23 | 24 | If you get a "Permission Denied" error while building, please see [this issue](https://github.com/omz/ColorSense-for-Xcode/issues/1) of the great [ColorSense plugin](https://github.com/omz/ColorSense-for-Xcode/). 25 | 26 | 27 | ## Uninstall 28 | 29 | In Xcode, go to *Plugins > JDPluginManager > Uninstall* and restart Xcode afterwards. 30 | -------------------------------------------------------------------------------- /assets/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calimarkus/JDPluginManager/b9ae4f19a2c02203269626c3f955e9166e607ca9/assets/screenshot1.png -------------------------------------------------------------------------------- /assets/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calimarkus/JDPluginManager/b9ae4f19a2c02203269626c3f955e9166e607ca9/assets/screenshot2.png -------------------------------------------------------------------------------- /assets/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calimarkus/JDPluginManager/b9ae4f19a2c02203269626c3f955e9166e607ca9/assets/screenshot3.png --------------------------------------------------------------------------------