Skip to content

Instantly share code, notes, and snippets.

@0xced
Created January 25, 2018 09:59
Show Gist options
  • Save 0xced/73d55c1d0be6953fb7b3199b6936f6a9 to your computer and use it in GitHub Desktop.
Save 0xced/73d55c1d0be6953fb7b3199b6936f6a9 to your computer and use it in GitHub Desktop.
Generate constants from storyboard identifiers
#!/usr/bin/env python
# Adapted from https://joris.kluivers.nl/blog/2014/02/10/storyboard-identifier-constants/ (https://github.com/kluivers/storyboard-constants)
# * Support for accessibility identifiers and accessibility labels
# * Easily extendable with xpath definitions
# * Valid identifiers
# * Namespaced constants, idea from https://www.mikeash.com/pyblog/friday-qa-2011-08-19-namespaced-constants-and-functions.html
PREFIX = ''
SPACING = '\t'
DEFINITIONS = {
'SegueIdentifier': [{'xpath': './/segue[@identifier]', 'attribute': 'identifier'}],
'ControllerIdentifier': [{'xpath': './/*[@storyboardIdentifier]', 'attribute': 'storyboardIdentifier'}],
'ReuseIdentifier': [{'xpath': './/*[@reuseIdentifier]', 'attribute': 'reuseIdentifier'}],
'AccessibilityIdentifier': [{'xpath': './/accessibility[@identifier]', 'attribute': 'identifier'}, {'xpath': './/userDefinedRuntimeAttribute[@keyPath="accessibilityIdentifier"]', 'attribute': 'value'}],
'AccessibilityLabel': [{'xpath': './/accessibility[@label]', 'attribute': 'label'}, {'xpath': './/userDefinedRuntimeAttribute[@keyPath="accessibilityLabel"]', 'attribute': 'value'}],
}
import sys, os, re, unicodedata
import xml.etree.ElementTree as ElementTree
# From http://stackoverflow.com/questions/517923/what-is-the-best-way-to-remove-accents-in-a-python-unicode-string/518232#518232
def stripAccents(s):
return ''.join(c for c in unicodedata.normalize('NFD', unicode(s)) if unicodedata.category(c) != 'Mn')
def makeIdentifier(s):
return re.sub('[^\w]', '_', stripAccents(s))
def processStoryboard(storyboardPath, identifiers):
root = ElementTree.parse(storyboardPath).getroot()
storyboardName = os.path.splitext(storyboardPath)[0]
for key,descriptors in DEFINITIONS.items():
for descriptor in descriptors:
identifiers[key].extend(map((lambda e: e.get(descriptor['attribute'])), root.findall(descriptor['xpath'])))
return identifiers
def writeIdentifiers(path, identifiers, importHeader, structBegin, structContent, structEnd):
with open(path, 'w+') as f:
f.write('/* Generated file. DO NOT MODIFY */\n\n')
f.write('#import {0}\n\n'.format(importHeader))
for key,identifiers in identifiers.items():
if len(identifiers) == 0:
continue
f.write(structBegin(key))
for identifier in sorted(identifiers):
f.write(structContent(identifier))
f.write(structEnd(key))
def main(headerPath, implementationPath):
identifiers = {key: [] for key in DEFINITIONS.keys()}
for n in range(int(os.environ['SCRIPT_INPUT_FILE_COUNT'])):
identifiers = processStoryboard(os.environ['SCRIPT_INPUT_FILE_' + str(n)], identifiers)
writeIdentifiers(headerPath, identifiers, '<Foundation/Foundation.h>',
(lambda kind: 'extern const struct {0}{1}Struct {{\n'.format(PREFIX, kind)),
(lambda value: '{0}__unsafe_unretained NSString *{1};\n'.format(SPACING, makeIdentifier(value))),
(lambda kind: '}} {0}{1};\n\n'.format(PREFIX, kind)))
writeIdentifiers(implementationPath, identifiers, '"' + os.path.basename(headerPath) + '"',
(lambda kind: 'const struct {0}{1}Struct {0}{1} = {{\n'.format(PREFIX, kind)),
(lambda value: '{0}.{1} = @"{2}",\n'.format(SPACING, makeIdentifier(value), value.encode('utf-8'))),
(lambda kind: '};\n\n'))
if __name__ == '__main__':
if len(sys.argv) != 3:
sys.exit('error: usage: {0} headerPath implementationPath'.format(os.path.basename(sys.argv[0])))
main(sys.argv[1], sys.argv[2])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment