├── LICENSE-MIT.txt ├── README.md ├── XObjC4.workflow └── Contents │ ├── Info.plist │ ├── QuickLook │ └── Preview.png │ └── document.wflow ├── example ├── DemoViewController.h └── DemoViewController.m ├── exampleXcode ├── Test1.h ├── Test1.m ├── Test2.h ├── Test2.m └── exampleXcode.xcodeproj │ └── project.pbxproj ├── website ├── demo-h.png ├── demo-m.png └── xcode4scheme.png ├── xobjc.h ├── xobjc.py ├── xobjc4.py └── xpublicmove.py /LICENSE-MIT.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Dirk Holtwick 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | **Deprecated, this code should not be used any more! Better use Xcode 4 with ARC!** 4 | 5 | --- 6 | 7 | 8 | xobjc 9 | ===== 10 | 11 | [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=holtwick&url=https://github.com/holtwick/xobjc&title=xobjc&language=en_GB&tags=github&category=software) 12 | 13 | **FULL XCODE4 COMPATIBILITY*** 14 | 15 | **Xcode4 Demo on YouTube [youtube.com/watch?v=Mzfv-ANydW4](http://www.youtube.com/watch?v=Mzfv-ANydW4).** 16 | 17 | **XCode3 Demo on mov.io [mov.io/fE](http://mov.io/fE).** 18 | 19 |
20 | 21 |
22 | 23 | A tool for making annoying every day tasks for Objective-C developers, especially 24 | iPhone developers, easier. Therefore this tool will help you writing semi-automatically 25 | the ``@property`` and ``@synthesize`` stuff. It also updates the ``dealloc`` method to release 26 | your objects. And you can define the attributes according to the 27 | [Google Objective-C Styleguide](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml#Variable_Name) 28 | using a trailing underscore, i.e. your public attribute is then defined 29 | without this underscore. But also a leading underscore is ok. Public method can easily be defined by 30 | prepending XPUBLIC in the implementation part. 31 | 32 | No more redundancies and nonsense typing orgies! You'll save a lot of time! 33 | 34 | Preparations 35 | ------------ 36 | 37 | You have to define some helpers in your code that will indicate your properties 38 | (you can alternatively use ``xobjc.h`` from this package): 39 | 40 | #define XRETAIN 41 | #define XIBOUTLET 42 | #define XASSIGN 43 | #define XCOPY 44 | #define XPROPERTY(...) 45 | #define XNIL nil 46 | #define XPUBLIC 47 | 48 | Your code 49 | --------- 50 | 51 | In your header file mark you properties like this: 52 | 53 | #import 54 | #import "xobjc.h" 55 | 56 | @interface DemoViewController : UIViewController { 57 | XIBOUTLET UILabel *header_; 58 | XIBOUTLET UITextView *message_; 59 | XASSIGN id delegate_; 60 | XASSIGN int counter; 61 | XRETAIN NSArray *listOfUsers_; 62 | XCOPY NSString *secretKey_; 63 | XPROPERTY(readonly) BOOL isPublic_; 64 | int age; 65 | } 66 | 67 | @end 68 | 69 | In your module file you can mark public methods like this: 70 | 71 | XPUBLIC 72 | - (void)somePubMethod { /* ... */ } 73 | 74 | (IBAction, class initializers and class methods are always considered public) 75 | 76 | Usage 77 | ----- 78 | 79 | ``$ python xobjc.py sample.h`` 80 | 81 | After that your ``.h`` and ``.m`` files are updated. XCode should reload your code in the editor 82 | automatically. The command also creates a new subfolder which contains a backup of your 83 | original code, hope you never will need it ;) 84 | 85 | Example 86 | ------- 87 | 88 | Some pictures say more than thousand words. Here is a before/after diff showing the magic: 89 | 90 | ![Header](https://github.com/holtwick/xobjc/raw/master/website/demo-h.png "Header") 91 | 92 | ![Module](https://github.com/holtwick/xobjc/raw/master/website/demo-m.png "Module") 93 | 94 | XCode4 Integration 95 | ------------------ 96 | 97 | Edit your projects 'Scheme' and add a new 'Run Script Action' to your 'Build / Pre-actions'. 98 | Fill in the full path of your xobjc4.py. Now each time you build your project this script 99 | will be run. This is super handy. 100 | 101 | ![Header](https://github.com/holtwick/xobjc/raw/master/website/xcode4scheme.png "Xcode4 Scheme Modifications") 102 | 103 | If Growl is installed it will also issue a notification. 104 | 105 | 106 | XCode3 Integration 107 | ------------------ 108 | 109 | In Xcode in the menu choose the script symbol and there ``Edit User Scripts``. Create a new 110 | entry and copy and paste the contents of the file ``xobjc.py`` into the text field. That's all, 111 | you are done. Just call the script form the menu or via the key shortcut you defined while you 112 | are in the header or implementation file you like to become updated. 113 | 114 | Related 115 | ------- 116 | 117 | Macro extensions for XCode 118 | 119 | * [xcode-text-macros](http://github.com/liyanage/xcode-text-macros) 120 | 121 | Similar approaches and discussions 122 | 123 | * 124 | * 125 | * 126 | 127 | Known Bugs and Problems 128 | ----------------------- 129 | 130 | * With Snow Leopard MacOS X 10.6 the 'osascript' helper, which is used for XCode integration, threw some 131 | errors. I used a workaround I found here to circumvent the problem: http://kb2.adobe.com/cps/516/cpsid_51615.html 132 | If you are using non Intel architecture or other OS you might need to modify this portion of code in xobjc.py 133 | 134 | License (MIT License) 135 | --------------------- 136 | 137 | Copyright (c) 2011 Dirk Holtwick 138 | 139 | Permission is hereby granted, free of charge, to any person obtaining a copy 140 | of this software and associated documentation files (the "Software"), to deal 141 | in the Software without restriction, including without limitation the rights 142 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 143 | copies of the Software, and to permit persons to whom the Software is 144 | furnished to do so, subject to the following conditions: 145 | 146 | The above copyright notice and this permission notice shall be included in 147 | all copies or substantial portions of the Software. 148 | 149 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 150 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 151 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 152 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 153 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 154 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 155 | THE SOFTWARE. 156 | 157 | -------------------------------------------------------------------------------- /XObjC4.workflow/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSServices 6 | 7 | 8 | NSMenuItem 9 | 10 | default 11 | XObjC4 12 | 13 | NSMessage 14 | runWorkflowAsService 15 | NSRequiredContext 16 | 17 | NSApplicationIdentifier 18 | com.apple.dt.Xcode 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /XObjC4.workflow/Contents/QuickLook/Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holtwick/xobjc/e83c2052783021c4e345b5cc826e1246b6c898b2/XObjC4.workflow/Contents/QuickLook/Preview.png -------------------------------------------------------------------------------- /XObjC4.workflow/Contents/document.wflow: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AMApplicationBuild 6 | 247.1 7 | AMApplicationVersion 8 | 2.1.1 9 | AMDocumentVersion 10 | 2 11 | actions 12 | 13 | 14 | action 15 | 16 | AMAccepts 17 | 18 | Container 19 | List 20 | Optional 21 | 22 | Types 23 | 24 | com.apple.applescript.object 25 | 26 | 27 | AMActionVersion 28 | 1.0 29 | AMParameterProperties 30 | 31 | source 32 | 33 | 34 | AMProvides 35 | 36 | Container 37 | List 38 | Types 39 | 40 | com.apple.applescript.object 41 | 42 | 43 | ActionBundlePath 44 | /System/Library/Automator/Run AppleScript.action 45 | ActionName 46 | Run AppleScript 47 | ActionParameters 48 | 49 | source 50 | tell application id "com.apple.dt.Xcode" 51 | -- save all open source files 52 | -- save source documents 53 | -- return paths as a list 54 | return path of source documents 55 | 56 | end tell 57 | 58 | Application 59 | 60 | Automator 61 | 62 | BundleIdentifier 63 | com.apple.Automator.RunScript 64 | CFBundleVersion 65 | 1.0 66 | CanShowSelectedItemsWhenRun 67 | 68 | CanShowWhenRun 69 | 70 | Category 71 | 72 | AMCategoryUtilities 73 | 74 | Class Name 75 | RunScriptAction 76 | InputUUID 77 | AD908FE2-621A-4494-A987-6F448B85429E 78 | Keywords 79 | 80 | Run 81 | 82 | OutputUUID 83 | 38A88520-6EC4-4A10-A1BD-0F629967E4FC 84 | StartAction 85 | 86 | UUID 87 | 93416056-A9F3-4504-A086-3F8E71D051C6 88 | UnlocalizedApplications 89 | 90 | Automator 91 | 92 | arguments 93 | 94 | 0 95 | 96 | default value 97 | on run {input, parameters} 98 | 99 | (* Your script goes here *) 100 | 101 | return input 102 | end run 103 | name 104 | source 105 | required 106 | 0 107 | type 108 | 0 109 | uuid 110 | 0 111 | 112 | 113 | isViewVisible 114 | 115 | location 116 | 302.000000:597.000000 117 | nibPath 118 | /System/Library/Automator/Run AppleScript.action/Contents/Resources/English.lproj/main.nib 119 | 120 | isViewVisible 121 | 122 | 123 | 124 | action 125 | 126 | AMAccepts 127 | 128 | Container 129 | List 130 | Optional 131 | 132 | Types 133 | 134 | com.apple.cocoa.string 135 | 136 | 137 | AMActionVersion 138 | 2.0.1 139 | AMParameterProperties 140 | 141 | COMMAND_STRING 142 | 143 | CheckedForUserDefaultShell 144 | 145 | inputMethod 146 | 147 | shell 148 | 149 | source 150 | 151 | 152 | AMProvides 153 | 154 | Container 155 | List 156 | Types 157 | 158 | com.apple.cocoa.string 159 | 160 | 161 | ActionBundlePath 162 | /System/Library/Automator/Run Shell Script.action 163 | ActionName 164 | Run Shell Script 165 | ActionParameters 166 | 167 | COMMAND_STRING 168 | /Users/dirk/work/xobjc/xobjc4.py $@ 169 | CheckedForUserDefaultShell 170 | 171 | inputMethod 172 | 1 173 | shell 174 | /bin/bash 175 | source 176 | 177 | 178 | Application 179 | 180 | Automator 181 | 182 | BundleIdentifier 183 | com.apple.RunShellScript 184 | CFBundleVersion 185 | 2.0.1 186 | CanShowSelectedItemsWhenRun 187 | 188 | CanShowWhenRun 189 | 190 | Category 191 | 192 | AMCategoryUtilities 193 | 194 | Class Name 195 | RunShellScriptAction 196 | InputUUID 197 | 3DF9515F-EA04-44F4-9370-5FC8E8F46CF4 198 | Keywords 199 | 200 | Shell 201 | Script 202 | Command 203 | Run 204 | Unix 205 | 206 | OutputUUID 207 | 82C3D6A5-64AF-4569-BD2C-B07CF4852F3D 208 | ShowWhenRun 209 | 210 | UUID 211 | 436E0C57-B266-4703-99D3-A95405A836CF 212 | UnlocalizedApplications 213 | 214 | Automator 215 | 216 | arguments 217 | 218 | 0 219 | 220 | default value 221 | 0 222 | name 223 | inputMethod 224 | required 225 | 0 226 | type 227 | 0 228 | uuid 229 | 0 230 | 231 | 1 232 | 233 | default value 234 | 235 | name 236 | source 237 | required 238 | 0 239 | type 240 | 0 241 | uuid 242 | 1 243 | 244 | 2 245 | 246 | default value 247 | 248 | name 249 | CheckedForUserDefaultShell 250 | required 251 | 0 252 | type 253 | 0 254 | uuid 255 | 2 256 | 257 | 3 258 | 259 | default value 260 | 261 | name 262 | COMMAND_STRING 263 | required 264 | 0 265 | type 266 | 0 267 | uuid 268 | 3 269 | 270 | 4 271 | 272 | default value 273 | /bin/sh 274 | name 275 | shell 276 | required 277 | 0 278 | type 279 | 0 280 | uuid 281 | 4 282 | 283 | 284 | isViewVisible 285 | 286 | location 287 | 302.000000:555.000000 288 | nibPath 289 | /System/Library/Automator/Run Shell Script.action/Contents/Resources/English.lproj/main.nib 290 | 291 | isViewVisible 292 | 293 | 294 | 295 | action 296 | 297 | AMAccepts 298 | 299 | Container 300 | List 301 | Optional 302 | 303 | Types 304 | 305 | com.apple.applescript.object 306 | 307 | 308 | AMActionVersion 309 | 1.0 310 | AMParameterProperties 311 | 312 | source 313 | 314 | 315 | AMProvides 316 | 317 | Container 318 | List 319 | Types 320 | 321 | com.apple.applescript.object 322 | 323 | 324 | ActionBundlePath 325 | /System/Library/Automator/Run AppleScript.action 326 | ActionName 327 | Run AppleScript 328 | ActionParameters 329 | 330 | source 331 | on run {input, parameters} tell application "GrowlHelperApp" -- Make a list of all the notification types -- that this script will ever send: set the allNotificationsList to {"XObjC"} -- Make a list of the notifications -- that will be enabled by default. -- Those not enabled by default can be enabled later -- in the 'Applications' tab of the growl prefpane. set the enabledNotificationsList to {"XObjC"} -- Register our script with growl. -- You can optionally (as here) set a default icon -- for this script's notifications. register as application ¬ "XObjC" all notifications allNotificationsList ¬ default notifications enabledNotificationsList ¬ icon of application "XCode" -- Send a Notification... notify with name ¬ "XObjC" title ¬ "XCode" description ¬ (input as Unicode text) application name "XObjC" end tell end run 332 | 333 | Application 334 | 335 | Automator 336 | 337 | BundleIdentifier 338 | com.apple.Automator.RunScript 339 | CFBundleVersion 340 | 1.0 341 | CanShowSelectedItemsWhenRun 342 | 343 | CanShowWhenRun 344 | 345 | Category 346 | 347 | AMCategoryUtilities 348 | 349 | Class Name 350 | RunScriptAction 351 | InputUUID 352 | CB4C8E9B-5632-4F56-A996-E9002F338948 353 | Keywords 354 | 355 | Run 356 | 357 | OutputUUID 358 | 93F2AFD6-9D01-4E34-A51F-0CF28D2E025F 359 | UUID 360 | 15CCB17F-66BB-4691-A710-336F7FC09B7E 361 | UnlocalizedApplications 362 | 363 | Automator 364 | 365 | arguments 366 | 367 | 0 368 | 369 | default value 370 | on run {input, parameters} 371 | 372 | (* Your script goes here *) 373 | 374 | return input 375 | end run 376 | name 377 | source 378 | required 379 | 0 380 | type 381 | 0 382 | uuid 383 | 0 384 | 385 | 386 | isViewVisible 387 | 388 | location 389 | 302.000000:513.000000 390 | nibPath 391 | /System/Library/Automator/Run AppleScript.action/Contents/Resources/English.lproj/main.nib 392 | 393 | isViewVisible 394 | 395 | 396 | 397 | connectors 398 | 399 | 7736A740-3CC4-4C1E-BE4D-F8287026A58A 400 | 401 | from 402 | 93416056-A9F3-4504-A086-3F8E71D051C6 - 93416056-A9F3-4504-A086-3F8E71D051C6 403 | to 404 | 436E0C57-B266-4703-99D3-A95405A836CF - 436E0C57-B266-4703-99D3-A95405A836CF 405 | 406 | C47FDE74-7A30-4449-8A4F-538C42E3DE99 407 | 408 | from 409 | 436E0C57-B266-4703-99D3-A95405A836CF - 436E0C57-B266-4703-99D3-A95405A836CF 410 | to 411 | 15CCB17F-66BB-4691-A710-336F7FC09B7E - 15CCB17F-66BB-4691-A710-336F7FC09B7E 412 | 413 | 414 | state 415 | 416 | AMLogTabViewSelectedIndex 417 | 1 418 | libraryState 419 | 420 | actionsMajorSplitViewState 421 | 422 | expandedPosition 423 | 0.0 424 | subviewState 425 | 426 | 0.000000, 0.000000, 381.000000, 515.000000, NO 427 | 0.000000, 516.000000, 381.000000, 239.000000, NO 428 | 429 | 430 | actionsMinorSplitViewState 431 | 432 | expandedPosition 433 | 0.0 434 | subviewState 435 | 436 | 0.000000, 0.000000, 163.000000, 515.000000, NO 437 | 164.000000, 0.000000, 217.000000, 515.000000, NO 438 | 439 | 440 | variablesMajorSplitViewState 441 | 442 | expandedPosition 443 | 0.0 444 | subviewState 445 | 446 | 0.000000, 0.000000, 381.000000, 555.000000, NO 447 | 0.000000, 556.000000, 381.000000, 199.000000, NO 448 | 449 | 450 | variablesMinorSplitViewState 451 | 452 | expandedPosition 453 | 0.0 454 | subviewState 455 | 456 | 0.000000, 0.000000, 163.000000, 555.000000, NO 457 | 164.000000, 0.000000, 217.000000, 555.000000, NO 458 | 459 | 460 | 461 | majorSplitViewState 462 | 463 | expandedPosition 464 | 381 465 | subviewState 466 | 467 | 0.000000, 0.000000, 381.000000, 800.000000, NO 468 | 382.000000, 0.000000, 619.000000, 800.000000, NO 469 | 470 | 471 | minorSplitViewState 472 | 473 | expandedPosition 474 | 0.0 475 | subviewState 476 | 477 | 0.000000, 0.000000, 619.000000, 609.000000, NO 478 | 0.000000, 619.000000, 619.000000, 162.000000, NO 479 | 480 | 481 | windowFrame 482 | {{213, 86}, {1000, 877}} 483 | workflowViewScrollPosition 484 | {{0, 0}, {604, 609}} 485 | 486 | workflowMetaData 487 | 488 | serviceApplicationBundleID 489 | com.apple.dt.Xcode 490 | serviceApplicationPath 491 | /Xcode4/Applications/Xcode.app 492 | serviceInputTypeIdentifier 493 | com.apple.Automator.nothing 494 | serviceOutputTypeIdentifier 495 | com.apple.Automator.nothing 496 | workflowTypeIdentifier 497 | com.apple.Automator.servicesMenu 498 | 499 | 500 | 501 | -------------------------------------------------------------------------------- /example/DemoViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Apply `xobjc` to this file to see the effect 4 | 5 | */ 6 | 7 | #import 8 | #import "xobjc.h" 9 | 10 | @interface DemoViewController : UIViewController { 11 | XIBOUTLET UILabel *header_; 12 | XIBOUTLET UITextView *message_; 13 | XASSIGN id _delegate; 14 | XASSIGN int counter; 15 | XRETAIN NSArray *listOfUsers_; 16 | XCOPY NSString *secretKey_; 17 | int age; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /example/DemoViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Apply `xobjc` to this file to see the effect 4 | 5 | */ 6 | 7 | #import "DemoViewController.h" 8 | 9 | @implementation DemoViewController 10 | 11 | // X PUBLIC 12 | - (IBAction)doSomething:(id)sender { 13 | NSLog(@"Doing something"); 14 | } 15 | 16 | - (void)viewDidUnload{ 17 | [super viewDidUnload]; 18 | self.delegate = nil; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /exampleXcode/Test1.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2010 holtwick.it. All rights reserved. 3 | // 4 | 5 | @interface Test1 : NSObject { 6 | id x; 7 | } 8 | 9 | @property (retain) id x; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /exampleXcode/Test1.m: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2010 holtwick.it. All rights reserved. 3 | // 4 | 5 | #import "Test1.h" 6 | 7 | @implementation Test1 8 | 9 | @synthesize x; 10 | 11 | // MARK: Methods 12 | 13 | // ... 14 | 15 | // MARK: Constructors 16 | 17 | /* 18 | + (id)instance { 19 | static id instance = nil; 20 | @synchronized (self) { 21 | if (!instance) { 22 | instance = [[self alloc] init]; 23 | } 24 | } 25 | return instance; 26 | } 27 | 28 | - (id)initWithString:(NSString *)string { 29 | self = [self init]; 30 | if (self != nil) { 31 | self.string = string; 32 | } 33 | return self; 34 | } 35 | 36 | - (id)init { 37 | self = [super init]; 38 | if (self != nil) { 39 | ; 40 | } 41 | return self; 42 | } 43 | */ 44 | 45 | - (void)dealloc{ 46 | [x release] 47 | [super dealloc]; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /exampleXcode/Test2.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2010 holtwick.it. All rights reserved. 3 | // 4 | 5 | #import "xobjc.h" 6 | 7 | @interface Test2 : NSObject { 8 | XRETAIN id x; 9 | } 10 | 11 | @property (retain) id x; 12 | 13 | @end -------------------------------------------------------------------------------- /exampleXcode/Test2.m: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2010 holtwick.it. All rights reserved. 3 | // 4 | 5 | #import "Test2.h" 6 | 7 | @implementation Test2 8 | 9 | // MARK: Methods 10 | 11 | // ... 12 | 13 | // MARK: Constructors 14 | 15 | /* 16 | + (id)instance { 17 | static id instance = nil; 18 | @synchronized (self) { 19 | if (!instance) { 20 | instance = [[self alloc] init]; 21 | } 22 | } 23 | return instance; 24 | } 25 | 26 | - (id)initWithString:(NSString *)string { 27 | self = [self init]; 28 | if (self != nil) { 29 | self.string = string; 30 | } 31 | return self; 32 | } 33 | 34 | - (id)init { 35 | self = [super init]; 36 | if (self != nil) { 37 | ; 38 | } 39 | return self; 40 | } 41 | */ 42 | 43 | - (void)dealloc { 44 | [x xrelease]; 45 | [super dealloc]; 46 | } 47 | 48 | @synthesize x; 49 | 50 | @end -------------------------------------------------------------------------------- /exampleXcode/exampleXcode.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 42; 7 | objects = { 8 | 9 | /* Begin PBXFileReference section */ 10 | 438D64E411FF684700F87B1A /* Test1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Test1.h; sourceTree = ""; }; 11 | 438D64E511FF684700F87B1A /* Test1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Test1.m; sourceTree = ""; }; 12 | 438D64F511FF68A300F87B1A /* Test2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Test2.h; sourceTree = ""; }; 13 | 438D64F611FF68A300F87B1A /* Test2.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Test2.m; sourceTree = ""; }; 14 | 438D64F711FF68C400F87B1A /* xobjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = xobjc.h; path = ../xobjc.h; sourceTree = SOURCE_ROOT; }; 15 | /* End PBXFileReference section */ 16 | 17 | /* Begin PBXGroup section */ 18 | 438D64DA11FF683600F87B1A = { 19 | isa = PBXGroup; 20 | children = ( 21 | 438D64F711FF68C400F87B1A /* xobjc.h */, 22 | 438D64F511FF68A300F87B1A /* Test2.h */, 23 | 438D64F611FF68A300F87B1A /* Test2.m */, 24 | 438D64E411FF684700F87B1A /* Test1.h */, 25 | 438D64E511FF684700F87B1A /* Test1.m */, 26 | ); 27 | sourceTree = ""; 28 | }; 29 | /* End PBXGroup section */ 30 | 31 | /* Begin PBXProject section */ 32 | 438D64DC11FF683600F87B1A /* Project object */ = { 33 | isa = PBXProject; 34 | buildConfigurationList = 438D64DF11FF683600F87B1A /* Build configuration list for PBXProject "exampleXcode" */; 35 | compatibilityVersion = "Xcode 2.4"; 36 | hasScannedForEncodings = 0; 37 | mainGroup = 438D64DA11FF683600F87B1A; 38 | projectDirPath = ""; 39 | projectRoot = ""; 40 | targets = ( 41 | ); 42 | }; 43 | /* End PBXProject section */ 44 | 45 | /* Begin XCBuildConfiguration section */ 46 | 438D64DD11FF683600F87B1A /* Debug */ = { 47 | isa = XCBuildConfiguration; 48 | buildSettings = { 49 | COPY_PHASE_STRIP = NO; 50 | }; 51 | name = Debug; 52 | }; 53 | 438D64DE11FF683600F87B1A /* Release */ = { 54 | isa = XCBuildConfiguration; 55 | buildSettings = { 56 | COPY_PHASE_STRIP = YES; 57 | }; 58 | name = Release; 59 | }; 60 | /* End XCBuildConfiguration section */ 61 | 62 | /* Begin XCConfigurationList section */ 63 | 438D64DF11FF683600F87B1A /* Build configuration list for PBXProject "exampleXcode" */ = { 64 | isa = XCConfigurationList; 65 | buildConfigurations = ( 66 | 438D64DD11FF683600F87B1A /* Debug */, 67 | 438D64DE11FF683600F87B1A /* Release */, 68 | ); 69 | defaultConfigurationIsVisible = 0; 70 | defaultConfigurationName = Release; 71 | }; 72 | /* End XCConfigurationList section */ 73 | }; 74 | rootObject = 438D64DC11FF683600F87B1A /* Project object */; 75 | } 76 | -------------------------------------------------------------------------------- /website/demo-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holtwick/xobjc/e83c2052783021c4e345b5cc826e1246b6c898b2/website/demo-h.png -------------------------------------------------------------------------------- /website/demo-m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holtwick/xobjc/e83c2052783021c4e345b5cc826e1246b6c898b2/website/demo-m.png -------------------------------------------------------------------------------- /website/xcode4scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holtwick/xobjc/e83c2052783021c4e345b5cc826e1246b6c898b2/website/xcode4scheme.png -------------------------------------------------------------------------------- /xobjc.h: -------------------------------------------------------------------------------- 1 | // 2 | // xobjc.h 3 | // 4 | // Created by Dirk Holtwick 5 | // Copyright holtwick.it 2009. All rights reserved. 6 | // 7 | 8 | #define XRETAIN 9 | #define XIBOUTLET 10 | #define XASSIGN 11 | #define XCOPY 12 | #define XPROPERTY(...) 13 | #define XDELEGATE 14 | 15 | #define XPUBLIC 16 | 17 | #define xobjc 18 | #define XNIL nil 19 | #define xnil nil 20 | #define xrelease release 21 | 22 | -------------------------------------------------------------------------------- /xobjc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | 4 | """ 5 | Copyright (c) 2009 Dirk Holtwick 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | CHANGELOG: 26 | 27 | 0.1 28 | - Initial release 29 | 30 | 0.2 (2009-09-04) 31 | - Dealloc can contain custom data 32 | - Adds missing dealloc correctly 33 | 34 | 0.3 (2009-09-11) 35 | - viewDidUnload support 36 | 37 | 0.4 (2009-12-18) 38 | - Joined with version of 'freewizard' with leading underscore support 39 | - Some refactoring to make the source more readable 40 | - Added XPUBLIC to mark public methods 41 | - Alphabetical sorting of blocks 42 | - Added MIT license 43 | 44 | 0.5 (2010-01-05) 45 | - More refactoring 46 | - Now work also when no properties are defined 47 | - Prefix XPUBLIC also in Interface file to prepare for more intelligent 48 | handling of those marked methods in future 49 | - Removed unsexy whitesapce 50 | 51 | 0.6 (2010-01-06) 52 | - Removed the prepending XPUBLIC from interface file because Interface 53 | Builder was not able to handle it 54 | - IBAction methods are always considered public (I don't see a case where 55 | they are not 56 | - Static methods are also considered public 57 | 58 | 0.7 (2010-02-07) 59 | - Fix for Apple Script calls 60 | - Handle comment correctly 61 | - New FORCE_METHODS setting 62 | 63 | 0.8 (2010-02-15) 64 | - Moved @synthesize to the end of files 65 | - Moved @property to the end of files 66 | 67 | 0.9 (2010-03-14) 68 | - Methods which start with 'initWith' are considered public 69 | - Added XPROPERTY(..) for individual property definitions, e.g. 70 | XPROPERTY(readonly) id test; 71 | - Removed XATOMIC and XREADONLY 72 | - Code cleanup 73 | 74 | 0.10 75 | - Now also works as build script 76 | - Only handle files if different file times 77 | - Only change files that contain X... macros 78 | - Nonatomic can be turned of as default 79 | - Strip trailing spaces 80 | - Multiple file and path arguments 81 | - Added XDELEGATE 82 | 83 | 0.11 (2010-09-07) 84 | - Fix: Can handle missing spaces around asterisk 85 | - External settings did not work, therefore removed 86 | 87 | 0.12 (2010-09-21) 88 | - Categories support 89 | - Expand environment variables for backup path 90 | - Support for .mm file suffixes 91 | - DEBUG does not write files 92 | 93 | 0.13 (2011-01-08) 94 | - XPROPERTY arguments case sensitive 95 | - Added 'xobjc' marker into @property 96 | - 'XASSIGN BOOL xxx' creates corect 'isXxx' getter 97 | 98 | TODO: 99 | 100 | - Work with more implementations etc. in one file and match name 101 | => Currently just one implementation per file 102 | - NSCoder support 103 | => Create all needed stuff 104 | - XPRIVATE 105 | => Put them into a category in the implementation file 106 | 107 | """ 108 | 109 | __version__ = "0.13" 110 | 111 | import re 112 | import os 113 | import os.path 114 | import shutil 115 | import pprint 116 | import datetime 117 | import subprocess 118 | 119 | ### CONFIG BEGIN 120 | 121 | # !!! PLEASE CHANGE THE FOLLOWING TO YOU NEEDS !!! 122 | 123 | # No backup at all 124 | # BACKUP_FOLDER = None 125 | 126 | # Subfolder of the sources location 127 | # BACKUP_FOLDER = 'BACKUP-XOBJC' 128 | 129 | # All into one absolute path 130 | BACKUP_FOLDER = os.path.expandvars('${HOME}/work/_build/__xobjc_backup') 131 | DEBUG = 0 132 | FORCE_METHODS = False #True 133 | BOOL_WITH_IS_GETTER = True 134 | STRIP_TRAILING_SPACES = True 135 | NONATOMIC = "" 136 | # NONATOMIC = "nonatomic, " 137 | 138 | ### CONFIG END 139 | 140 | rxInterface = re.compile(""" 141 | .*? 142 | @interface (?P .*?) 143 | \{ 144 | (?P .*? ) 145 | \} 146 | (?P .*?) 147 | @end 148 | .*? 149 | """, re.VERBOSE | re.M | re.DOTALL) 150 | 151 | rxInterfaceCat = re.compile(""" 152 | .*? 153 | @interface 154 | (?P .*?) 155 | \( 156 | (?P .*?) 157 | \) 158 | 159 | (?P .*?) 160 | @end 161 | .*? 162 | """, re.VERBOSE | re.M | re.DOTALL) 163 | rxImplementation = re.compile(""" 164 | .*? 165 | \@implementation\s+(?P[a-zA-Z0-9_]+) 166 | (?P .*?) 167 | \@end 168 | """, re.VERBOSE | re.M | re.DOTALL) 169 | 170 | rxDealloc = re.compile(""" 171 | \-\s*\(void\)\s* 172 | dealloc 173 | \s* 174 | \{ 175 | (?P .*?) 176 | (\[\s*[^\s]+\s+x?release\s*\]\s*\;\s*)* 177 | \[\s*super\s+dealloc\s*\]\s*\;\s* 178 | \} 179 | """, re.VERBOSE | re.M | re.DOTALL) 180 | 181 | rxViewDidUnload = re.compile(""" 182 | \-\s*\(void\)\s* 183 | viewDidUnload 184 | \s* 185 | \{ 186 | (?P [^\}]*?) 187 | \} 188 | """, re.VERBOSE | re.M | re.DOTALL) 189 | 190 | rxViewDidUnloadBody = re.compile(""" 191 | \[\s*super\s+viewDidUnload\s*\]\s*\; 192 | | 193 | self\.[a-zA-Z0-9_]+ \s* \= \s* (xnil|XNIL) \s* \; 194 | """, re.VERBOSE | re.M | re.DOTALL) 195 | 196 | rxVariables = re.compile(""" 197 | (XCOPY | XASSIGN | XRETAIN | XIBOUTLET | XDELEGATE | IBOutlet | XPROPERTY\(.*?\)) 198 | \s+ 199 | ([a-zA-Z0-9_][a-zA-Z0-9_\<\>]*) 200 | (( 201 | (?: 202 | \s*\* 203 | | 204 | \s 205 | ) 206 | \s* 207 | [a-zA-Z0-9_]+ 208 | \s* 209 | \,? 210 | \s* 211 | )+) 212 | \; 213 | """, re.VERBOSE | re.M | re.DOTALL) 214 | 215 | rxProperty = re.compile(""" 216 | \@property 217 | 218 | \s* 219 | 220 | ( 221 | \( 222 | .*? 223 | \) 224 | )? 225 | 226 | \s* 227 | 228 | ([a-zA-Z0-9_][a-zA-Z0-9_\<\>]*) 229 | 230 | \s+ 231 | 232 | (( 233 | \*? 234 | \s* 235 | [a-zA-Z0-9_]+ 236 | \s* 237 | \,? 238 | \s* 239 | )+) 240 | \; 241 | 242 | """, re.VERBOSE | re.M | re.DOTALL) 243 | 244 | rxSynthesize = re.compile(""" 245 | \@synthesize 246 | \s+ 247 | \w+ 248 | \s* 249 | \=? 250 | \s* 251 | \w* 252 | \; 253 | """, re.VERBOSE | re.M | re.DOTALL) 254 | 255 | rxLeadingUnderscore = re.compile("(\s*\*?\s*)_(.+)") 256 | 257 | rxMethod = re.compile(""" 258 | (?P 259 | (XPUBLIC)? 260 | ) 261 | \s* 262 | (?P 263 | [\-\+] 264 | \s* 265 | \([^\)]+\) 266 | \s* 267 | [a-zA-Z_] 268 | [^\{\=]+? 269 | ) 270 | \{ 271 | 272 | """, re.VERBOSE | re.M | re.DOTALL) 273 | 274 | #rxInstance = re.compile(""" 275 | # XINSTANCE([^\s]+) 276 | #""", re.VERBOSE | re.M | re.DOTALL) 277 | 278 | rxComment = re.compile(""" 279 | ( 280 | \/\*.*?\*\/ 281 | | 282 | ^\s*\/\/.*?$ 283 | ) 284 | """, re.VERBOSE | re.M | re.DOTALL) 285 | 286 | rxInitMethod = re.compile(""" 287 | \-\s*(.*?)\s*initWith.* 288 | """, re.VERBOSE | re.M | re.DOTALL) 289 | 290 | def mySorted(v, **k): 291 | return v 292 | # return sorted(v, **k) 293 | 294 | class Module: 295 | 296 | def __init__(filename): 297 | self.base = filename[:filename.rfind(".")] 298 | self.h = self.base + '.h' 299 | self.m = self.baee = ".m" 300 | 301 | def stripComments(value): 302 | #if DEBUG: 303 | # for c in rxComment.findall(value): 304 | # print c 305 | return rxComment.sub('', value) 306 | 307 | def extractVariables(data): 308 | return [x.strip() for x in data.strip().split(",")] 309 | 310 | def insertString(base, pos, new): 311 | return base[:pos] + new + base[pos:] 312 | 313 | def analyze(hdata, mdata): 314 | 315 | ### HEADER 316 | 317 | vars = dict() 318 | 319 | propBlock = [] 320 | viewdidunload = [] 321 | dealloc = [] 322 | block = [] 323 | isCategory = 0 324 | 325 | interfaceMatch = rxInterface.match(hdata) 326 | if not interfaceMatch: 327 | interfaceMatch = rxInterfaceCat.match(hdata) 328 | if not interfaceMatch: 329 | return None, None 330 | else: 331 | isCategory = 1 332 | 333 | if not isCategory: 334 | varblock = interfaceMatch.group("varblock") 335 | varblock = stripComments(varblock.strip()) 336 | 337 | # Collect variable definitions 338 | for mv in rxVariables.finditer(varblock): 339 | mode, type_, names, names_ = mv.groups() 340 | for vname in extractVariables(names): 341 | vars[''.join(vname.split())] = (mode.lower(), type_, mode) 342 | 343 | # Remove @properties completely from interface 344 | properties = interfaceMatch.group("properties") 345 | 346 | if isCategory: 347 | for mpp in rxProperty.finditer(properties): 348 | propBlock.append(mpp.group(0)) 349 | 350 | if not isCategory: 351 | properties = rxProperty.sub('', properties).lstrip() 352 | 353 | # Create @properties 354 | for vname in mySorted(vars.keys(), key=lambda k:k.strip('*').strip('_')): 355 | mode, type_, origMode = vars[vname] 356 | 357 | iboutlet = 0 358 | star = '*' if vname.startswith('*') else '' 359 | name = vname.lstrip('*') # Withoout leading * 360 | pvname = name # Without underscore 361 | 362 | # Google compatible synthesize 363 | if name.endswith('_'): 364 | pvname = name[:-1] 365 | block.append("@synthesize %s = %s;" % (pvname, name)) 366 | elif name.startswith('_'): 367 | pvname = name[1:] 368 | block.append("@synthesize %s = %s;" % (pvname, name)) 369 | else: 370 | block.append("@synthesize %s;" % (name)) 371 | 372 | # Properties 373 | propMarker = "xobjc " 374 | if mode == 'iboutlet': 375 | iboutlet = 1 376 | mode = 'retain' 377 | propBlock.append("@property (%s%s%s) %s %s%s;" % (propMarker, NONATOMIC, mode, type_, star, pvname)) 378 | elif mode == 'xiboutlet': 379 | iboutlet = 1 380 | mode = "retain" 381 | type_ = "IBOutlet %s" % type_ 382 | propBlock.append("@property (%s%s%s) %s %s%s;" % (propMarker, NONATOMIC, mode, type_, star, pvname)) 383 | elif mode == 'xdelegate': 384 | iboutlet = 1 385 | mode = "assign" 386 | type_ = "IBOutlet %s" % type_ 387 | propBlock.append("@property (%s%s%s) %s %s%s;" % (propMarker, NONATOMIC, mode, type_, star, pvname)) 388 | elif mode.startswith('xproperty('): 389 | # XXX Iboutlet 390 | pattr = origMode.strip()[10:-1] 391 | propBlock.append("@property (%s%s) %s %s%s;" % (propMarker, pattr, type_, star, pvname)) 392 | mode = 'assign' 393 | pattrlist = [x.strip().lower() for x in pattr.split(',')] 394 | if 'retain' in pattrlist or 'copy' in pattrlist: 395 | mode = 'retain' 396 | else: 397 | mode = mode[1:] 398 | if BOOL_WITH_IS_GETTER and type_ == "BOOL": 399 | mode = "getter=is%s%s" % (pvname[0].capitalize(), pvname[1:]) 400 | propBlock.append("@property (%s%s%s) %s %s%s;" % (propMarker, NONATOMIC, mode, type_, star, pvname)) 401 | 402 | # print mode 403 | 404 | # Release stuff 405 | if mode in ('retain', 'copy'): 406 | dealloc.append(" [%s xrelease];" % name) 407 | 408 | if iboutlet: 409 | viewdidunload.append(" self.%s = xnil;" % pvname) 410 | 411 | # print viewdidunload 412 | 413 | propBlock = "\n".join(propBlock) 414 | 415 | ### MODULE 416 | 417 | # Find implementation blinterfaceMatch 418 | implementationMatch = rxImplementation.match(mdata) 419 | impName = implementationMatch.group('name') 420 | 421 | #if DEBUG and implementationMatch: 422 | # print "Implementation", implementationMatch.groups() 423 | 424 | # Replace @synthesize block 425 | body = implementationMatch.group("body") 426 | 427 | if not isCategory: 428 | body = rxSynthesize.sub('', body).strip() 429 | block = "\n".join(block) + '\n\n' 430 | 431 | # Update 'dealloc' 432 | md = rxDealloc.search(body) 433 | if md: 434 | # deallocbody = rxRelease.sub('', md.group("deallocbody")).strip() 435 | deallocbody = md.group("deallocbody").strip() 436 | if deallocbody: 437 | deallocbody = " " + deallocbody + "\n\n" 438 | newdealloc = ("- (void)dealloc { " 439 | + ("\n" + deallocbody).rstrip() 440 | + ("\n" + "\n".join(mySorted(dealloc))).rstrip() 441 | + "\n [super dealloc];\n}") 442 | body = rxDealloc.sub(newdealloc, body) 443 | else: 444 | newdealloc = "- (void)dealloc {\n" + "\n".join(mySorted(dealloc)) + "\n [super dealloc];\n}" 445 | body += "\n\n" + newdealloc 446 | 447 | # Update 'viewDidUnload' (iPhone and iPad only) 448 | md = rxViewDidUnload.search(body) 449 | if md: 450 | viewdidunloadbody = rxViewDidUnloadBody.sub('', md.group("viewdidunloadbody")).strip() 451 | if viewdidunloadbody: 452 | viewdidunloadbody = "\n " + viewdidunloadbody + "\n\n" 453 | newviewdidunloadbody = ( 454 | "- (void)viewDidUnload {\n [super viewDidUnload];\n" 455 | + (" " + viewdidunloadbody.strip()).rstrip() 456 | + ("\n" + "\n".join(mySorted(viewdidunload))).rstrip() 457 | + "\n}") 458 | body = rxViewDidUnload.sub(newviewdidunloadbody, body) 459 | 460 | ### METHODS 461 | mDefs = [] 462 | xpub = 0 463 | bodyStripped = stripComments(body) 464 | 465 | for mMethod in rxMethod.finditer(bodyStripped): 466 | mName = mMethod.group('name').strip() 467 | # if mMethod.group("comment"): 468 | # mName = "\n" + mMethod.group("comment").strip() + "\n" + mName 469 | #if DEBUG: 470 | # print mName, mMethod.groups() 471 | if (mMethod.group('kind') == 'XPUBLIC'): 472 | xpub += 1 473 | mDefs.append(mName + ';') 474 | elif mName.startswith("+") or mName.lstrip('-').lstrip().startswith("(IBAction)"): 475 | mDefs.append(mName + ';') 476 | elif rxInitMethod.match(mName): 477 | mDefs.append(mName + ';') 478 | 479 | ### XINSTANCE 480 | #mdi = rxInstance.search(bodyStripped) 481 | #if mdi: 482 | # xpub += 1 483 | # mDefs.append("+ (id)instance;") 484 | 485 | # If no XPUBLIC was defined don't replace old stuff 486 | if mDefs or FORCE_METHODS: 487 | # mDefs = "\n".join(mySorted(mDefs)) + '\n\n' 488 | mDefs = "\n".join(mDefs) + '\n\n' 489 | else: 490 | mDefs = properties 491 | 492 | ### RESULT 493 | 494 | if isCategory: 495 | 496 | hdata = (hdata[:interfaceMatch.start("properties")] 497 | + ('\n\n' + propBlock).rstrip() 498 | + ('\n\n' + mDefs).rstrip() 499 | + '\n\n' + hdata[interfaceMatch.end("properties"):]) 500 | 501 | else: 502 | 503 | hdata = (hdata[:interfaceMatch.start("properties")] 504 | + ('\n\n' + mDefs).rstrip() 505 | + ('\n\n' + propBlock).rstrip() 506 | + '\n\n' + hdata[interfaceMatch.end("properties"):]) 507 | 508 | mdata = (mdata[:implementationMatch.start('body')] 509 | + ('\n\n' + body).rstrip() 510 | + ('\n\n' + block).rstrip() 511 | + '\n\n' + mdata[implementationMatch.end('body'):]) 512 | 513 | # Did something change? 514 | if xpub or propBlock: 515 | return hdata, mdata 516 | 517 | return None, None 518 | 519 | def modifyFiles(filename): 520 | 521 | # Calculate basic filenames 522 | base = os.path.normpath(os.path.abspath(filename)) 523 | folder = os.path.dirname(base) 524 | filePart = os.path.basename(base) 525 | 526 | if filePart == "main.m": 527 | # print "File %r will not be modified" % filePart 528 | return False 529 | 530 | hfile = filename[:filename.rfind(".")] + '.h' 531 | mfile = filename[:filename.rfind(".")] + '.m' 532 | 533 | # Check if files exist 534 | if not os.path.isfile(hfile): 535 | # print "File %r does not exist" % hfile 536 | return False 537 | if not os.path.isfile(mfile): 538 | # print "File %r does not exist" % hfile 539 | mfile = filename[:filename.rfind(".")] + '.mm' 540 | if not os.path.isfile(mfile): 541 | return False 542 | 543 | htime = os.stat(hfile).st_mtime 544 | mtime = os.stat(mfile).st_mtime 545 | 546 | if htime == mtime: 547 | # print "No update needed" 548 | return False 549 | 550 | # Handle and modify files 551 | hsrc = open(hfile).read() 552 | msrc = open(mfile).read() 553 | if ("noxobjc" in hsrc.lower()) or ("noxobjc" in msrc.lower()): 554 | # print "File ignored" 555 | return False 556 | 557 | # Handle and modify files 558 | hdata, mdata = analyze( 559 | hsrc, 560 | msrc) 561 | 562 | if not (hdata and mdata): 563 | return False 564 | 565 | # Backup files 566 | if BACKUP_FOLDER: 567 | backupFolder = os.path.join( 568 | folder, 569 | BACKUP_FOLDER, 570 | 'backup-' + datetime.datetime.today().strftime("%Y%m%d-%H%M%S")) 571 | if not os.path.isdir(backupFolder): 572 | os.makedirs(backupFolder) 573 | shutil.copyfile(hfile, os.path.join(backupFolder, filePart[:-2] + '.h')) 574 | shutil.copyfile(mfile, os.path.join(backupFolder, filePart[:-2] + '.m')) 575 | # print "Created backup of files in %r" % backupFolder 576 | 577 | 578 | if STRIP_TRAILING_SPACES: 579 | hdata = "\n".join([l.rstrip() for l in hdata.splitlines()]) 580 | mdata = "\n".join([l.rstrip() for l in mdata.splitlines()]) 581 | 582 | if DEBUG: 583 | print "=" * 80 584 | print hfile 585 | print "=" * 80 586 | print hdata 587 | print "=" * 80 588 | print mfile 589 | print "=" * 80 590 | print mdata 591 | 592 | if not DEBUG: 593 | f = open(hfile, 'w') 594 | f.write(hdata) 595 | f.close() 596 | 597 | f = open(mfile, 'w') 598 | f.write(mdata) 599 | f.close() 600 | 601 | # Same file time 602 | subprocess.call(['touch', hfile, mfile]) 603 | 604 | #print "Modified %r" % hfile 605 | #print "Modified %r" % mfile 606 | 607 | return True 608 | 609 | def xcodeReload(): 610 | # Trick to reload files in XCode 611 | # Bug workaround for SL, see http://kb2.adobe.com/cps/516/cpsid_51615.html 612 | print "XCode refresh" 613 | subprocess.call(['arch', '-i386', 'osascript', '-e', 'activate application "Finder"\nactivate application "XCode"']) 614 | 615 | if __name__ == "__main__": 616 | import sys 617 | import glob 618 | 619 | # You can also place it into 'XCode User Scripts' but it does not relead the window yet 620 | try: 621 | filename = '%%%{PBXFilePath}%%%' 622 | except: 623 | filename = '' 624 | 625 | if filename and (not filename.startswith('%')): 626 | modifyFiles(filename) 627 | xcodeReload() 628 | 629 | elif len(sys.argv) >= 2: 630 | # srcroot = os.environ.get("SRCROOT") 631 | 632 | modified = False 633 | for filename in sys.argv[1:]: 634 | 635 | filename = os.path.abspath(filename) 636 | # print "Analyze %s" % filename 637 | 638 | mfiles = [filename] 639 | if os.path.isdir(filename): 640 | for root, dirs, files in os.walk(filename): 641 | for name in files: 642 | if (BACKUP_FOLDER not in root) and name.endswith(".m"): 643 | mfiles.append(os.path.join(root, name)) 644 | 645 | # print "\n".join(mfiles) 646 | 647 | # elif srcroot: 648 | # files = glob.glob("Classes/*.m") 649 | 650 | if mfiles: 651 | 652 | for fn in mfiles: 653 | if modifyFiles(fn): 654 | print "Modified %r" % fn 655 | modified = True 656 | 657 | if modified: 658 | xcodeReload() 659 | else: 660 | print "No modifications needed" 661 | 662 | else: 663 | print "Usage: xobjc.py [file/folder paths]" 664 | -------------------------------------------------------------------------------- /xobjc4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | 4 | """ 5 | Copyright (c) 2009-2012 Dirk Holtwick 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | CHANGELOG: 26 | 27 | 0.1 28 | - Initial release 29 | 30 | 0.2 (2009-09-04) 31 | - Dealloc can contain custom data 32 | - Adds missing dealloc correctly 33 | 34 | 0.3 (2009-09-11) 35 | - viewDidUnload support 36 | 37 | 0.4 (2009-12-18) 38 | - Joined with version of 'freewizard' with leading underscore support 39 | - Some refactoring to make the source more readable 40 | - Added XPUBLIC to mark public methods 41 | - Alphabetical sorting of blocks 42 | - Added MIT license 43 | 44 | 0.5 (2010-01-05) 45 | - More refactoring 46 | - Now work also when no properties are defined 47 | - Prefix XPUBLIC also in Interface file to prepare for more intelligent 48 | handling of those marked methods in future 49 | - Removed unsexy whitesapce 50 | 51 | 0.6 (2010-01-06) 52 | - Removed the prepending XPUBLIC from interface file because Interface 53 | Builder was not able to handle it 54 | - IBAction methods are always considered public (I don't see a case where 55 | they are not 56 | - Static methods are also considered public 57 | 58 | 0.7 (2010-02-07) 59 | - Fix for Apple Script calls 60 | - Handle comment correctly 61 | - New FORCE_METHODS setting 62 | 63 | 0.8 (2010-02-15) 64 | - Moved @synthesize to the end of files 65 | - Moved @property to the end of files 66 | 67 | 0.9 (2010-03-14) 68 | - Methods which start with 'initWith' are considered public 69 | - Added XPROPERTY(..) for individual property definitions, e.g. 70 | XPROPERTY(readonly) id test; 71 | - Removed XATOMIC and XREADONLY 72 | - Code cleanup 73 | 74 | 0.10 75 | - Now also works as build script 76 | - Only handle files if different file times 77 | - Only change files that contain X... macros 78 | - Nonatomic can be turned of as default 79 | - Strip trailing spaces 80 | - Multiple file and path arguments 81 | - Added XDELEGATE 82 | 83 | 0.11 (2010-09-07) 84 | - Fix: Can handle missing spaces around asterisk 85 | - External settings did not work, therefore removed 86 | 87 | 0.12 (2010-09-21) 88 | - Categories support 89 | - Expand environment variables for backup path 90 | - Support for .mm file suffixes 91 | - DEBUG does not write files 92 | 93 | 0.13 (2011-01-08) 94 | - XPROPERTY arguments case sensitive 95 | - Added 'xobjc' marker into @property 96 | - 'XASSIGN BOOL xxx' creates corect 'isXxx' getter 97 | 98 | 0.14 (2012-02-23) 99 | - Xcode 4.3 is picky about the position of XPUBLIC therefore 100 | moved it into the return parameter definition like 101 | - (XPUBLIC void)someMethod 102 | - Empty deallocs will not be written 103 | 104 | TODO: 105 | 106 | - Work with more implementations etc. in one file and match name 107 | => Currently just one implementation per file 108 | - NSCoder support 109 | => Create all needed stuff 110 | - XPRIVATE 111 | => Put them into a category in the implementation file 112 | 113 | """ 114 | 115 | __version__ = "0.14" 116 | 117 | import re 118 | import os 119 | import os.path 120 | import shutil 121 | import pprint 122 | import datetime 123 | import subprocess 124 | 125 | ### CONFIG BEGIN 126 | 127 | # !!! PLEASE CHANGE THE FOLLOWING TO YOU NEEDS !!! 128 | 129 | # No backup at all 130 | # BACKUP_FOLDER = None 131 | 132 | # Subfolder of the sources location 133 | # BACKUP_FOLDER = 'BACKUP-XOBJC' 134 | 135 | # All into one absolute path 136 | BACKUP_FOLDER = os.path.expandvars('${HOME}/work/_build/__xobjc_backup') 137 | DEBUG = 0 138 | FORCE_METHODS = False #True 139 | BOOL_WITH_IS_GETTER = True 140 | STRIP_TRAILING_SPACES = True 141 | # NONATOMIC = "" 142 | NONATOMIC = "nonatomic, " 143 | 144 | ### CONFIG END 145 | 146 | rxInterface = re.compile(""" 147 | .*? 148 | @interface (?P .*?) 149 | \{ 150 | (?P .*? ) 151 | \} 152 | (?P .*?) 153 | @end 154 | .*? 155 | """, re.VERBOSE | re.M | re.DOTALL) 156 | 157 | rxInterfaceCat = re.compile(""" 158 | .*? 159 | @interface 160 | (?P .*?) 161 | \( 162 | (?P .*?) 163 | \) 164 | 165 | (?P .*?) 166 | @end 167 | .*? 168 | """, re.VERBOSE | re.M | re.DOTALL) 169 | rxImplementation = re.compile(""" 170 | .*? 171 | \@implementation\s+(?P[a-zA-Z0-9_]+) 172 | (?P .*?) 173 | \@end 174 | """, re.VERBOSE | re.M | re.DOTALL) 175 | 176 | rxDealloc = re.compile(""" 177 | \-\s*\(void\)\s* 178 | dealloc 179 | \s* 180 | \{ 181 | (?P .*?) 182 | (\[\s*[^\s]+\s+x?release\s*\]\s*\;\s*)* 183 | \[\s*super\s+dealloc\s*\]\s*\;\s* 184 | \} 185 | """, re.VERBOSE | re.M | re.DOTALL) 186 | 187 | rxViewDidUnload = re.compile(""" 188 | \-\s*\(void\)\s* 189 | viewDidUnload 190 | \s* 191 | \{ 192 | (?P [^\}]*?) 193 | \} 194 | """, re.VERBOSE | re.M | re.DOTALL) 195 | 196 | rxViewDidUnloadBody = re.compile(""" 197 | \[\s*super\s+viewDidUnload\s*\]\s*\; 198 | | 199 | self\.[a-zA-Z0-9_]+ \s* \= \s* (xnil|XNIL) \s* \; 200 | """, re.VERBOSE | re.M | re.DOTALL) 201 | 202 | rxVariables = re.compile(""" 203 | (XCOPY | XASSIGN | XRETAIN | XIBOUTLET | XDELEGATE | XPROPERTY\(.*?\)) 204 | \s+ 205 | ([^\s]*) 206 | (( 207 | (?: 208 | \s*\* 209 | | 210 | \s 211 | ) 212 | \s* 213 | [a-zA-Z0-9_]+ 214 | \s* 215 | \,? 216 | \s* 217 | )+) 218 | \; 219 | """, re.VERBOSE | re.M | re.DOTALL) 220 | 221 | rxProperty = re.compile(""" 222 | \@property 223 | 224 | \s* 225 | 226 | ( 227 | \( 228 | .*? 229 | \) 230 | )? 231 | 232 | \s* 233 | 234 | ([a-zA-Z0-9_][a-zA-Z0-9_\<\>]*) 235 | 236 | \s+ 237 | 238 | (( 239 | \*? 240 | \s* 241 | [a-zA-Z0-9_]+ 242 | \s* 243 | \,? 244 | \s* 245 | )+) 246 | \; 247 | 248 | """, re.VERBOSE | re.M | re.DOTALL) 249 | 250 | rxSynthesize = re.compile(""" 251 | \@synthesize 252 | \s+ 253 | \w+ 254 | \s* 255 | \=? 256 | \s* 257 | \w* 258 | \; 259 | """, re.VERBOSE | re.M | re.DOTALL) 260 | 261 | rxLeadingUnderscore = re.compile("(\s*\*?\s*)_(.+)") 262 | 263 | rxMethod = re.compile(""" 264 | (?P 265 | (XPUBLIC)? 266 | ) 267 | \s* 268 | (?P 269 | [\-\+] 270 | \s* 271 | \([^\)]+\) 272 | \s* 273 | [a-zA-Z_] 274 | [^\{\=]+? 275 | ) 276 | \{ 277 | 278 | """, re.VERBOSE | re.M | re.DOTALL) 279 | 280 | #rxInstance = re.compile(""" 281 | # XINSTANCE([^\s]+) 282 | #""", re.VERBOSE | re.M | re.DOTALL) 283 | 284 | rxComment = re.compile(""" 285 | ( 286 | \/\*.*?\*\/ 287 | | 288 | ^\s*\/\/.*?$ 289 | ) 290 | """, re.VERBOSE | re.M | re.DOTALL) 291 | 292 | rxInitMethod = re.compile(""" 293 | \-\s*(.*?)\s*initWith.* 294 | """, re.VERBOSE | re.M | re.DOTALL) 295 | 296 | def mySorted(v, **k): 297 | return v 298 | # return sorted(v, **k) 299 | 300 | class Module: 301 | 302 | def __init__(filename): 303 | self.base = filename[:filename.rfind(".")] 304 | self.h = self.base + '.h' 305 | self.m = self.baee = ".m" 306 | 307 | def stripComments(value): 308 | #if DEBUG: 309 | # for c in rxComment.findall(value): 310 | # print c 311 | return rxComment.sub('', value) 312 | 313 | def extractVariables(data): 314 | return [x.strip() for x in data.strip().split(",")] 315 | 316 | def insertString(base, pos, new): 317 | return base[:pos] + new + base[pos:] 318 | 319 | def analyze(hdata, mdata): 320 | 321 | ### HEADER 322 | 323 | vars = dict() 324 | 325 | propBlock = [] 326 | viewdidunload = [] 327 | dealloc = [] 328 | block = [] 329 | isCategory = 0 330 | 331 | interfaceMatch = rxInterface.match(hdata) 332 | if not interfaceMatch: 333 | interfaceMatch = rxInterfaceCat.match(hdata) 334 | if not interfaceMatch: 335 | return None, None 336 | else: 337 | isCategory = 1 338 | 339 | if not isCategory: 340 | varblock = interfaceMatch.group("varblock") 341 | varblock = stripComments(varblock.strip()) 342 | 343 | # Collect variable definitions 344 | for mv in rxVariables.finditer(varblock): 345 | mode, type_, names, names_ = mv.groups() 346 | for vname in extractVariables(names): 347 | vars[''.join(vname.split())] = (mode.lower(), type_, mode) 348 | 349 | # Remove @properties completely from interface 350 | properties = interfaceMatch.group("properties") 351 | 352 | if isCategory: 353 | for mpp in rxProperty.finditer(properties): 354 | propBlock.append(mpp.group(0)) 355 | 356 | if not isCategory: 357 | properties = rxProperty.sub('', properties).lstrip() 358 | 359 | # Create @properties 360 | for vname in mySorted(vars.keys(), key=lambda k:k.strip('*').strip('_')): 361 | mode, type_, origMode = vars[vname] 362 | 363 | iboutlet = 0 364 | star = '*' if vname.startswith('*') else '' 365 | name = vname.lstrip('*') # Withoout leading * 366 | pvname = name # Without underscore 367 | 368 | # Google compatible synthesize 369 | if name.endswith('_'): 370 | pvname = name[:-1] 371 | block.append("@synthesize %s = %s;" % (pvname, name)) 372 | elif name.startswith('_'): 373 | pvname = name[1:] 374 | block.append("@synthesize %s = %s;" % (pvname, name)) 375 | else: 376 | block.append("@synthesize %s;" % (name)) 377 | 378 | # Properties 379 | propMarker = "xobjc " 380 | if mode == 'xiboutlet': 381 | iboutlet = 1 382 | mode = "retain" 383 | type_ = "IBOutlet %s" % type_ 384 | propBlock.append("@property (%s%s%s) %s %s%s;" % (propMarker, NONATOMIC, mode, type_, star, pvname)) 385 | elif mode == 'xdelegate': 386 | iboutlet = 1 387 | mode = "assign" 388 | type_ = "IBOutlet %s" % type_ 389 | propBlock.append("@property (%s%s%s) %s %s%s;" % (propMarker, NONATOMIC, mode, type_, star, pvname)) 390 | elif mode.startswith('xproperty('): 391 | # XXX Iboutlet 392 | pattr = origMode.strip()[10:-1] 393 | propBlock.append("@property (%s%s) %s %s%s;" % (propMarker, pattr, type_, star, pvname)) 394 | mode = 'assign' 395 | pattrlist = [x.strip().lower() for x in pattr.split(',')] 396 | if 'retain' in pattrlist or 'copy' in pattrlist: 397 | mode = 'retain' 398 | else: 399 | mode = mode[1:] 400 | if BOOL_WITH_IS_GETTER and type_ == "BOOL": 401 | mode = "getter=is%s%s" % (pvname[0].capitalize(), pvname[1:]) 402 | propBlock.append("@property (%s%s%s) %s %s%s;" % (propMarker, NONATOMIC, mode, type_, star, pvname)) 403 | 404 | # print mode 405 | 406 | # Release stuff 407 | if mode in ('retain', 'copy'): 408 | dealloc.append(" [%s xrelease];" % name) 409 | 410 | if iboutlet: 411 | viewdidunload.append(" self.%s = xnil;" % pvname) 412 | 413 | # print viewdidunload 414 | 415 | propBlock = "\n".join(propBlock) 416 | 417 | ### MODULE 418 | 419 | # Find implementation blinterfaceMatch 420 | implementationMatch = rxImplementation.match(mdata) 421 | impName = implementationMatch.group('name') 422 | 423 | #if DEBUG and implementationMatch: 424 | # print "Implementation", implementationMatch.groups() 425 | 426 | # Replace @synthesize block 427 | body = implementationMatch.group("body") 428 | 429 | if not isCategory: 430 | body = rxSynthesize.sub('', body).strip() 431 | block = "\n".join(block) + '\n\n' 432 | 433 | # Update 'dealloc' 434 | md = rxDealloc.search(body) 435 | if md: 436 | deallocbody = md.group("deallocbody").strip() 437 | if deallocbody or dealloc: 438 | # deallocbody = rxRelease.sub('', md.group("deallocbody")).strip() 439 | if deallocbody: 440 | deallocbody = " " + deallocbody + "\n\n" 441 | newdealloc = ("- (void)dealloc { " 442 | + ("\n" + deallocbody).rstrip() 443 | + ("\n" + "\n".join(mySorted(dealloc))).rstrip() 444 | + "\n [super dealloc];\n}") 445 | body = rxDealloc.sub(newdealloc, body) 446 | else: 447 | if dealloc: 448 | newdealloc = "- (void)dealloc {\n" + "\n".join(mySorted(dealloc)) + "\n [super dealloc];\n}" 449 | body += "\n\n" + newdealloc 450 | 451 | # Update 'viewDidUnload' (iPhone and iPad only) 452 | md = rxViewDidUnload.search(body) 453 | if md: 454 | viewdidunloadbody = rxViewDidUnloadBody.sub('', md.group("viewdidunloadbody")).strip() 455 | if viewdidunloadbody: 456 | viewdidunloadbody = "\n " + viewdidunloadbody + "\n\n" 457 | newviewdidunloadbody = ( 458 | "- (void)viewDidUnload {\n [super viewDidUnload];\n" 459 | + (" " + viewdidunloadbody.strip()).rstrip() 460 | + ("\n" + "\n".join(mySorted(viewdidunload))).rstrip() 461 | + "\n}") 462 | body = rxViewDidUnload.sub(newviewdidunloadbody, body) 463 | 464 | ### METHODS 465 | mDefs = [] 466 | xpub = 0 467 | bodyStripped = stripComments(body) 468 | 469 | for mMethod in rxMethod.finditer(bodyStripped): 470 | mName = mMethod.group('name').strip() 471 | # if mMethod.group("comment"): 472 | # mName = "\n" + mMethod.group("comment").strip() + "\n" + mName 473 | #if DEBUG: 474 | 475 | print mName, mMethod.groups() 476 | if (mMethod.group('kind') == 'XPUBLIC'): 477 | xpub += 1 478 | mDefs.append(mName + ';') 479 | elif mName.startswith("+") or mName.lstrip('-').lstrip().startswith("(IBAction)"): 480 | mDefs.append(mName + ';') 481 | elif mName.lstrip('-').lstrip().startswith("(XPUBLIC "): 482 | mDefs.append(mName.replace("XPUBLIC ", "") + ';') 483 | xpub += 1 484 | elif rxInitMethod.match(mName): 485 | mDefs.append(mName + ';') 486 | 487 | ### XINSTANCE 488 | #mdi = rxInstance.search(bodyStripped) 489 | #if mdi: 490 | # xpub += 1 491 | # mDefs.append("+ (id)instance;") 492 | 493 | # If no XPUBLIC was defined don't replace old stuff 494 | if mDefs or FORCE_METHODS: 495 | # mDefs = "\n".join(mySorted(mDefs)) + '\n\n' 496 | mDefs = "\n".join(mDefs) + '\n\n' 497 | else: 498 | mDefs = properties 499 | 500 | ### RESULT 501 | 502 | if isCategory: 503 | 504 | hdata = (hdata[:interfaceMatch.start("properties")] 505 | + ('\n\n' + propBlock).rstrip() 506 | + ('\n\n' + mDefs).rstrip() 507 | + '\n\n' + hdata[interfaceMatch.end("properties"):]) 508 | 509 | else: 510 | 511 | hdata = (hdata[:interfaceMatch.start("properties")] 512 | + ('\n\n' + mDefs).rstrip() 513 | + ('\n\n' + propBlock).rstrip() 514 | + '\n\n' + hdata[interfaceMatch.end("properties"):]) 515 | 516 | mdata = (mdata[:implementationMatch.start('body')] 517 | + ('\n\n' + body).rstrip() 518 | + ('\n\n' + block).rstrip() 519 | + '\n\n' + mdata[implementationMatch.end('body'):]) 520 | 521 | # Did something change? 522 | if xpub or propBlock: 523 | return hdata, mdata 524 | 525 | return None, None 526 | 527 | def modifyFiles(filename): 528 | 529 | # Calculate basic filenames 530 | base = os.path.normpath(os.path.abspath(filename)) 531 | folder = os.path.dirname(base) 532 | filePart = os.path.basename(base) 533 | 534 | if filePart == "main.m": 535 | # print "File %r will not be modified" % filePart 536 | return False 537 | 538 | hfile = filename[:filename.rfind(".")] + '.h' 539 | mfile = filename[:filename.rfind(".")] + '.m' 540 | 541 | # Check if files exist 542 | if not os.path.isfile(hfile): 543 | # print "File %r does not exist" % hfile 544 | return False 545 | if not os.path.isfile(mfile): 546 | # print "File %r does not exist" % hfile 547 | mfile = filename[:filename.rfind(".")] + '.mm' 548 | if not os.path.isfile(mfile): 549 | return False 550 | 551 | htime = os.stat(hfile).st_mtime 552 | mtime = os.stat(mfile).st_mtime 553 | 554 | if htime == mtime: 555 | # print "No update needed" 556 | return False 557 | 558 | # Handle and modify files 559 | hsrc = open(hfile).read() 560 | msrc = open(mfile).read() 561 | if ("noxobjc" in hsrc.lower()) or ("noxobjc" in msrc.lower()): 562 | # print "File ignored" 563 | return False 564 | 565 | # Handle and modify files 566 | hdata, mdata = analyze( 567 | hsrc, 568 | msrc) 569 | 570 | if not (hdata and mdata): 571 | return False 572 | 573 | # Backup files 574 | if BACKUP_FOLDER: 575 | backupFolder = os.path.join( 576 | folder, 577 | BACKUP_FOLDER, 578 | 'backup-' + datetime.datetime.today().strftime("%Y%m%d-%H%M%S")) 579 | if not os.path.isdir(backupFolder): 580 | os.makedirs(backupFolder) 581 | shutil.copyfile(hfile, os.path.join(backupFolder, filePart[:-2] + '.h')) 582 | shutil.copyfile(mfile, os.path.join(backupFolder, filePart[:-2] + '.m')) 583 | # print "Created backup of files in %r" % backupFolder 584 | 585 | 586 | if STRIP_TRAILING_SPACES: 587 | hdata = "\n".join([l.rstrip() for l in hdata.splitlines()]) 588 | mdata = "\n".join([l.rstrip() for l in mdata.splitlines()]) 589 | 590 | if DEBUG: 591 | print "=" * 80 592 | print hfile 593 | print "=" * 80 594 | print hdata 595 | print "=" * 80 596 | print mfile 597 | print "=" * 80 598 | print mdata 599 | 600 | if not DEBUG: 601 | f = open(hfile, 'w') 602 | f.write(hdata) 603 | f.close() 604 | 605 | f = open(mfile, 'w') 606 | f.write(mdata) 607 | f.close() 608 | 609 | # Same file time 610 | subprocess.call(['touch', hfile, mfile]) 611 | 612 | #print "Modified %r" % hfile 613 | #print "Modified %r" % mfile 614 | 615 | return True 616 | 617 | def xcodeReload(): 618 | # Trick to reload files in XCode 619 | # Bug workaround for SL, see http://kb2.adobe.com/cps/516/cpsid_51615.html 620 | # print "XCode refresh" 621 | # subprocess.call(['arch', '-i386', 'osascript', '-e', 'activate application "Finder"\nactivate application "XCode"']) 622 | pass 623 | 624 | def callAppleScript(script, input=None): 625 | import StringIO 626 | if not input: 627 | return subprocess.check_output( 628 | ['osascript', '-e', script], 629 | stderr=subprocess.STDOUT) 630 | 631 | p = subprocess.Popen(['osascript', '-e', script], stdout=subprocess.PIPE, stdin=subprocess.PIPE) 632 | p.stdin.write(input) 633 | ret = p.communicate()[0] 634 | p.stdin.close() 635 | return ret 636 | 637 | def callGrowl(msg): 638 | callAppleScript(""" 639 | tell application "GrowlHelperApp" 640 | set the allNotificationsList to {"XObjC"} 641 | set the enabledNotificationsList to {"XObjC"} 642 | register as application "XObjC" all notifications allNotificationsList default notifications enabledNotificationsList icon of application "XCode" 643 | notify with name "XObjC" title "XCode" description "%s" application name "XObjC" 644 | end tell 645 | """ % repr(msg)[1:-1]) 646 | 647 | OUT = [] 648 | 649 | def out(*a): 650 | OUT.append(" ".join([str(v) for v in a])) 651 | 652 | def main(): 653 | filenames = callAppleScript(""" 654 | tell application id "com.apple.dt.Xcode" 655 | return path of source documents 656 | end tell 657 | """) 658 | 659 | if not filenames: 660 | print "Nothing to do" 661 | 662 | filenames = [n.strip() for n in filenames.split(',')] 663 | 664 | modified = False 665 | for filename in filenames: 666 | 667 | filename = os.path.abspath(filename) 668 | # print "Analyze %s" % filename 669 | 670 | mfiles = [filename] 671 | 672 | # XXX Obsolete? 673 | if os.path.isdir(filename): 674 | for root, dirs, files in os.walk(filename): 675 | for name in files: 676 | if (BACKUP_FOLDER not in root) and name.endswith(".m"): 677 | mfiles.append(os.path.join(root, name)) 678 | 679 | # print "FILES:" 680 | # print "\n".join(mfiles) 681 | 682 | # elif srcroot: 683 | # files = glob.glob("Classes/*.m") 684 | 685 | if mfiles: 686 | 687 | for fn in mfiles: 688 | if modifyFiles(fn): 689 | out("Modified: %s" % os.path.basename(fn)) 690 | modified = True 691 | 692 | if modified: 693 | # xcodeReload() 694 | pass 695 | else: 696 | out("No modifications needed") 697 | 698 | s = '\n'.join(OUT) 699 | if s: 700 | print s 701 | callGrowl(s) 702 | 703 | if __name__ == "__main__": 704 | main() 705 | -------------------------------------------------------------------------------- /xpublicmove.py: -------------------------------------------------------------------------------- 1 | """ 2 | Transforms: 3 | 4 | XPUBLIC 5 | - (void)bla { 6 | 7 | Into: 8 | 9 | - (XPUBLIC void)bla { 10 | 11 | For Xcode 4.3 compatibility 12 | """ 13 | 14 | import glob 15 | import re 16 | 17 | rx = re.compile("\-\s*\(") 18 | files = ( 19 | # Add you implementation files here 20 | # glob.glob("/Users/SOMEUSER/work/SOMETPATH/*.m") 21 | ) 22 | 23 | for name in files: 24 | print name 25 | next = 0 26 | lines = [] 27 | for line in open(name, "r").readlines(): 28 | if line.startswith("XPUBLIC"): 29 | next = 1 30 | continue 31 | # print line 32 | if next and line.startswith("-"): 33 | line = rx.sub("- (XPUBLIC ", line) 34 | print " ", line, 35 | lines += line.rstrip() + '\n' 36 | next = 0; 37 | 38 | open(name, "w").writelines(lines) 39 | --------------------------------------------------------------------------------