Created
October 20, 2011 21:26
-
-
Save heydamianc/1302431 to your computer and use it in GitHub Desktop.
An Objective-C code formatter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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