Skip to content

Instantly share code, notes, and snippets.

@damiancarrillo
Created October 20, 2011 21:26
Show Gist options
  • Save damiancarrillo/1302431 to your computer and use it in GitHub Desktop.
Save damiancarrillo/1302431 to your computer and use it in GitHub Desktop.
An Objective-C code formatter
#! /usr/bin/env awk -f
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Copyright © 2011, Damian Carrillo
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <organization> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# File: objc-formatter.awk
# Version: 0.1
# Date: 2011-10-28
#
# DESCRIPTION
# Formats Objective-C source code and header files.
#
# Usage:
# $ ./objc-formatter.awk [-v verbose=1] [-v configFile=<config file>] <source file>
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BEGIN {
# Possible values: 0, 1
#
# 0: Do not replace leading tabs charactes wth spaces
#
# 1: Replace all leading tab characters with spaces
config["REPLACE_LEADING_TABS_WITH_SPACES"] = 1
# Possible Values 0+
#
config["NUMBER_OF_SPACES_PER_TAB"] = 4
# Possible values: 0, 1
#
# 0: Do not reformat properties. All configuration entries that begin with 'PROPERTIES'
# will be ignored
# 1: Reformat properties. All configuration entries that begin with 'PROPERTIES' will
# be respected
#
config["PROPERTIES_SHOULD_BE_FORMATTED"] = 1
# Possible Values: 0, 1
#
# 0: Does not group IBOutlets at the top of all property declarations
#
# 1: Groups all IBOutlets at the top of all property declarations
#
config["PROPERTIES_SHOULD_HAVE_IBOUTLETS_GROUPED"] = 1
# Possible Values: 0, 1
#
# 0: @property(nonatomic, retain) UIViewController *viewController;
#
# 1: @property (nonatomic, retain) UIViewController *viewController;
#
config["PROPERTIES_SHOULD_HAVE_SPACE_AFTER_ANNOTATION"] = 1
# Possible Values: 1 - 4
#
# 1: All property declarations shift as far left as possible
# @property(nonatomic, retain) IBOutlet UIWindow *window;
# @property(nonatomic, retain, readwrite) UITableView *tableView;
#
# 2: All property annotations are left aligned, all following information is left aligned
# @property(nonatomic, retain) IBOutlet UIWindow *window;
# @property(nonatomic, retain, readwrite) UITableView *tableView;
#
# 3: All property annotations are left aligned, then all decorators and types are considered
# as being a single column, then all names are lined up
# @property(nonatomic, retain) IBOutlet UIWindow *window;
# @property(nonatomic, retain, readwrite) UITableView *tableView;
#
# 4: All property annotations are left aligned, then all decorators, types, and names are
# lined up in a columnar fashion
# @property(nonatomic, retain) IBOutlet UIWindow *window;
# @property(nonatomic, retain, readwrite) UITableView *tableView;
#
config["PROPERTIES_SHOULD_HAVE_N_COLUMNS"] = 4
# Possible values: 0, 1
#
# 0: Do not reformat synthesizers. All configuration entries that begin with 'SYNTHESIZERS'
# will be ignored
# 1: Reformat synthesizers. All configuration entries that begin with 'SYNTHESIZERS' will
# be respected
#
config["SYNTHESIZERS_SHOULD_BE_FORMATTED"] = 1
# Possible Values: 1 - 2
#
# 1: Push all synthesizers to the left as far as possible
# @synthesize tableView = _tableView;
# @synthesize fetchedResultsController = _fetchedResultsController;
#
# 2: Line up both the left hand and right hand side of the alias assignment
# @synthesize tableView = _tableView;
# @synthesize fetchedResultsController = _fetchedResultsController;
#
config["SYNTHESIZERS_SHOULD_HAVE_N_COLUMNS"] = 2
if (configFile) {
readConfig(configFile)
}
if (verbose) {
printConfig(config)
}
tabReplacement = ""
numberOfSpacesPerTab = config["NUMBER_OF_SPACES_PER_TAB"]
for (i = 0; i < numberOfSpacesPerTab; i++) {
tabReplacement = tabReplacement " "
}
while (getline line) {
parseLine(line)
}
}
function parseLine(line) {
if (config["PROPERTIES_SHOULD_BE_FORMATTED"] == 1 && line ~ /@property/) {
maxLengths["propertyDeclaration"] = 0
maxLengths["decoratorList"] = 0
maxLengths["type"] = 0
maxLengths["name"] = 0
readProperties(line, propertyDeclarations, decoratorLists, types, names, maxLengths)
} else if (config["SYNTHESIZERS_SHOULD_BE_FORMATTED"] == 1 && line ~ /@synthesize/) {
readSynthesizers(line, properties, aliases)
} else {
line = trimTrailingWhitespace(line)
line = detab(line, tabReplacement)
print line
}
}
function readProperties(line, propertyDeclarations, decoratorLists, types, names, maxLengths) {
extractProperties(line, propertyDeclarations, decoratorLists, types, names, maxLengths)
getline line
if (line ~ /@property/ || line ~ /^[ \t]*$/) {
readProperties(line, propertyDeclarations, decoratorLists, types, names, maxLengths)
} else {
formatProperties(propertyDeclarations, decoratorLists, types, names, maxLengths)
parseLine(line)
}
}
function extractProperties(line, propertyDeclarations, decoratorLists, types, names, maxLengths) {
if (line !~ /^[ \t]*$/) {
gsub(";", "", line)
line = condenseWhitespace(line)
rightParensLoc = index(line, ")")
propertyDeclaration = substr(line, 0, rightParensLoc)
sub(/@property[ \t]*\(/, "@property(", propertyDeclaration)
i = length(propertyDeclarations) + 1
propertyDeclaration = trim(propertyDeclaration)
maxLengths["propertyDeclaration"] = max(maxLengths["propertyDeclaration"], length(propertyDeclaration))
propertyDeclarations[i] = propertyDeclaration
variableDeclaration = trim(substr(line, rightParensLoc + 1, length(line)))
# associate the star with the variable name's token
gsub(/[ \t]*\*[ \t]*/, " *", variableDeclaration)
split(variableDeclaration, variableComponents)
variableComponentCount = length(variableComponents)
name = trim(variableComponents[variableComponentCount])
maxLengths["name"] = max(maxLengths["name"], length(name))
names[i] = name
type = trim(variableComponents[variableComponentCount - 1])
maxLengths["type"] = max(maxLengths["type"], length(type))
types[i] = type
decoratorList = ""
for (j = 1; j < variableComponentCount - 1; j++) {
decoratorList = decoratorList " " variableComponents[j]
}
decoratorList = trim(condenseWhitespace(decoratorList))
decoratorLists[i] = decoratorList
maxLengths["decoratorList"] = max(maxLengths["decoratorList"], length(decoratorList))
}
}
function formatProperties(propertyDeclarations, decoratorLists, types, names, maxLengths) {
propertyCount = length(propertyDeclarations)
if (config["PROPERTIES_SHOULD_HAVE_IBOUTLETS_GROUPED"] == 1) {
a = 1
b = 1
for (i = 1; i <= propertyCount; i++) {
if (decoratorLists[i] ~ /IBOutlet/) {
propertyDeclarationsA[a] = propertyDeclarations[i]
decoratorListsA[a] = decoratorLists[i]
typesA[a] = types[i]
namesA[a] = names[i]
a++
} else {
propertyDeclarationsB[b] = propertyDeclarations[i]
decoratorListsB[b] = decoratorLists[i]
typesB[b] = types[i]
namesB[b] = names[i]
b++
}
}
delete propertyDeclarations
delete decoratorLists
delete types
delete names
j = 1
propertiesInA = length(propertyDeclarationsA)
for (a = 1; a <= propertiesInA; a++) {
propertyDeclarations[j] = propertyDeclarationsA[a]
decoratorLists[j] = decoratorListsA[a]
types[j] = typesA[a]
names[j] = namesA[a]
j++
}
propertiesInB = length(propertyDeclarationsB)
for (b = 1; b <= propertiesInB; b++) {
propertyDeclarations[j] = propertyDeclarationsB[b]
decoratorLists[j] = decoratorListsB[b]
types[j] = typesB[b]
names[j] = namesB[b]
j++
}
}
if (config["PROPERTIES_SHOULD_HAVE_SPACE_AFTER_ANNOTATION"] == 1) {
for (i = 1; i <= propertyCount; i++) {
sub(/@property\(/, "@property (", propertyDeclarations[i])
}
maxLengths["propertyDeclaration"] = maxLengths["propertyDeclaration"] + 1
}
# precalculate the 2nd column's length (for the case when there are 3 columns)
col2Len = 0
for (i = 1; i < propertyCount; i++) {
col2Len = max(col2Len, length(trim(decoratorLists[i] " " types[i] " ")))
}
for (i = 1; i <= propertyCount; i++) {
if (config["PROPERTIES_SHOULD_HAVE_N_COLUMNS"] == 1) {
if (length(decoratorLists[i]) > 0) {
printf("%s %s %s %s;\n", propertyDeclarations[i], decoratorLists[i], types[i], names[i])
} else {
printf("%s %s %s;\n", propertyDeclarations[i], types[i], names[i])
}
} else if (config["PROPERTIES_SHOULD_HAVE_N_COLUMNS"] == 2) {
col1 = propertyDeclarations[i]
col2 = trim(decoratorLists[i] " " types[i] " " names[i])
format = "%-" maxLengths["propertyDeclaration"] "s %s;\n"
printf(format, col1, col2)
} else if (config["PROPERTIES_SHOULD_HAVE_N_COLUMNS"] == 3) {
col1 = propertyDeclarations[i]
col2 = trim(decoratorLists[i] " " types[i] " ")
col3 = names[i]
format = "%-" maxLengths["propertyDeclaration"] "s %-" col2Len "s %s;\n"
printf(format, col1, col2, col3)
} else {
col1 = propertyDeclarations[i]
col2 = decoratorLists[i]
col3 = types[i]
col4 = names[i]
if (maxLengths["decoratorList"] == 0) {
format = "%-" maxLengths["propertyDeclaration"] "s %-" maxLengths["type"] "s %s;\n"
printf(format, col1, col3, col4)
} else {
format = "%-" maxLengths["propertyDeclaration"] "s %-" maxLengths["decoratorList"] "s " \
"%-" maxLengths["type"] "s %s;\n"
printf(format, col1, col2, col3, col4)
}
}
}
print ""
}
function readSynthesizers(line, properties, aliases) {
while (line ~ /@synthesize/ && !index(line, ";")) {
getline anotherLine
line = line anotherLine
}
extractSynthesizers(line, properties, aliases)
getline line
if (line ~ /@synthesize/ || line ~ /^[ \t]*$/) {
readSynthesizers(line, properties, aliases)
} else {
formatSynthesizers(properties, aliases)
parseLine(line)
}
}
function extractSynthesizers(line, properties, aliases) {
gsub("@synthesize", "", line)
gsub(";", "", line)
gsub(/[ \t]*/, "", line)
split(line, directives, ",")
len = length(directives)
for (i = 1; i <= len; i++) {
# The components of the directive are the LHS and RHS of the alias assignment, so for
# @syntehsize a = _a, component[1] would be 'a' and component[2] would be 'b'
split(directives[i], components, "=")
if (length(components[1]) > 0) { # avoid lines with only whitespace
j = length(properties) + 1
properties[j] = components[1]
if (length(components) > 1) {
aliases[j] = components[2]
} else {
aliases[j] = components[1]
}
}
}
}
function formatSynthesizers(properties, aliases) {
len = length(properties)
maxLength = maxLengthOf(properties)
for (i = 1; i <= len; i++) {
format = ""
if (config["SYNTHESIZERS_SHOULD_HAVE_N_COLUMNS"] == 1) {
format = "@synthesize %s = %s;\n"
} else {
format = "@synthesize %-" maxLength "s = %s;\n"
}
printf(format, properties[i], aliases[i])
}
print ""
}
function readConfig(configFile) {
while (getline line < configFile) {
if (line ~ /^[a-zA-Z1-0_-]+[ \t]*=/) {
sub(/[ \t]*=[ \t]*/, "=", line)
split(line, lineElements, " ")
split(lineElements[1], configElements, "=")
config[configElements[1]] = configElements[2]
}
}
close(configFile)
}
function printConfig(config) {
print "┏━ Config ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
maxEntry = 0
for (entry in config) {
maxEntry = max(maxEntry, length(entry))
}
remaining = 72 - maxEntry
format = "┃ %-" maxEntry "s = %-" remaining "s ┃\n"
for (entry in config) {
printf(format, entry, config[entry])
}
print "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
}
function trim(s) {
return trimLeadingWhitespace(trimTrailingWhitespace(s))
}
function trimLeadingWhitespace(s) {
sub(/^[ \t]*/, "", s)
return s
}
function trimTrailingWhitespace(s) {
sub(/[ \t]*$/, "", s)
return s
}
function condenseWhitespace(s) {
gsub(/[ \t]+/, " ", s)
return s
}
function detab(s, r) {
gsub(/\t/, r, s)
return s
}
function maxLengthOf(candidates) {
maxLength = 0
len = length(candidates)
for (i = 1; i <= len; i++) {
maxLength = max(maxLength, length(candidates[i]));
}
return maxLength
}
function max(m, n) {
return m > n ? m : n
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment