"User Script to generate properties from instance variables"

Wed 02 September 2009

#!/usr/bin/python

 

 

# Takes a header file with one or more instance variables selected

# and creates properties and synthesize directives for the selected properties.

 

# Accepts google-style instance variables with a tailing underscore and

# creates an appropriately named property without underscore.

 

# Xcode script options should be as follows:

# Entire Document

# Home Directory

# Discard Output

# Display in Alert

 

import os

import re

import subprocess

 

# AppleScripts for altering contents of files via Xcode

setFileContentsScript = """\

on run argv

set fileAlias to POSIX file (item 1 of argv)

set newDocText to (item 2 of argv)

tell application "Xcode"

set doc to open fileAlias

set text of doc to newDocText

end tell

end run \

"""

 

getFileContentsScript = """\

on run argv

set fileAlias to POSIX file (item 1 of argv)

tell application "Xcode"

set doc to open fileAlias

set docText to text of doc

end tell

return docText

end run \

"""

 

# Get variables from Xcode

headerFileText = """%%%{PBXAllText}%%%"""

selectionStartIndex = %%%{PBXSelectionStart}%%%

selectionEndIndex = %%%{PBXSelectionEnd}%%%

selectedText = headerFileText[selectionStartIndex:selectionEndIndex]

 

headerFilePath = """%%%{PBXFilePath}%%%"""

 

# Look for an implementation file with .m or .mm extension

implementationFilePath = headerFilePath[:-1] + "m"

if not os.path.exists(implementationFilePath):

    implementationFilePath += "m"

 

instanceVariablesRegex = re.compile(

"""^\s*((?:(?:\\b\w+\\b)\s+)*(?:(?:\\b\\w+\\b)))\\s*""" + # Identifier(s)

"""([*]?)\\s*""" + # An optional asterisk

"""(\\b\\w+?)(_?\\b);""", # The variable name

re.M)

 

# Now for each instance variable in the selected section

properties = ""

synthesizes = ""

 

for lineMatch in instanceVariablesRegex.findall(selectedText):

    types = " ".join(lineMatch[0].split()) # Clean up consequtive whitespace

    

    asterisk = lineMatch[1]

    variableName = lineMatch[2]

    trailingUnderscore = lineMatch[3]

    

    pointerPropertyAttributes = "(copy) " # Attributes if variable is pointer

    if not asterisk:

        pointerPropertyAttributes = ""

    

    newProperty = "@property %s%s %s%s;\n" % (pointerPropertyAttributes,

                                           types,

                                           asterisk,

                                           variableName)

    

    # If there's a trailing underscore, we need to let the synthesize

    # know which backing variable it's using

    newSynthesize = "@synthesize %s%s;\n" % (variableName,

                                         trailingUnderscore and

                                         " = %s_" % variableName)

    

    properties += newProperty

    synthesizes += newSynthesize

 

# Check to make sure at least 1 properties was found to generate

if not properties:

    os.sys.stderr.writelines("No properties found to generate")

    exit(-1)

 

# We want to insert the new properties either immediately after the last

# existing property or at the end of the instance variable section

findLastPropertyRegex = re.compile("^@interface.*?{.*?}.*?\\n" +

                             "(?:.*^\\s*@property.*?\\n)?", re.M | re.S)

headerInsertIndex = findLastPropertyRegex.search(headerFileText).end()

 

# Add new lines on either side if this is the only property in the file

addedNewLine = "\n"

if re.search("^\s*@property", headerFileText, re.M):

    # Not the only property, don't add

    addedNewLine = ""

 

newHeaderFileText = "%s%s%s%s" % (headerFileText[:headerInsertIndex],

                          addedNewLine,

                          properties,

                          headerFileText[headerInsertIndex:])

 

subprocess.call(["osascript",

          "-e",

          setFileContentsScript,

          headerFilePath,

          newHeaderFileText])

 

 

if not os.path.exists(implementationFilePath):

    os.sys.stdout.writelines("No implementation file found")

    exit(0)

 

implementationFileText = subprocess.Popen(

["osascript",

"-e",

getFileContentsScript,

implementationFilePath],

stdout=subprocess.PIPE).communicate()[0]

 

# We want to insert the synthesizes either immediately after the last existing

# @synthesize or after the @implementation directive

lastSynthesizeRegex = re.compile("^\\s*@implementation.*?\\n" +

                          "(?:.*^\\s*@synthesize.*?\\n)?", re.M | re.S)

 

implementationInsertIndex = \

lastSynthesizeRegex.search(implementationFileText).end()

 

# Add new lines on either side if this is the only synthsize in the file

addedNewLine = "\n"

if re.search("^\s*@synthesize", implementationFileText, re.M):

    # Not the only synthesize, don't add

    addedNewLine = ""

 

newImplementationFileText = "%s%s%s%s" % \

            (implementationFileText[:implementationInsertIndex],

             addedNewLine,

             synthesizes,

             implementationFileText[implementationInsertIndex:])

 

subprocess.call(["osascript",

           "-e",

           setFileContentsScript,

           implementationFilePath,

           newImplementationFileText])

 

# Switch Xcode back to header file

subprocess.Popen(["osascript",

            "-e",

            getFileContentsScript,

            headerFilePath],

           stdout=subprocess.PIPE).communicate()