# -*- coding: utf-8 -*-
"""
PEP 8 Attributes
~~~~~~~~~~~~~~~~
This adds :PEP:`8` compatible aliases for object attributes which are not.
:Copyright: 2007 Jochen Kupperschmidt
:Date: 11-Jul-2007
:License: MIT
.. _PEP 8: http://www.python.org/dev/peps/pep-0008/
"""
from itertools import ifilterfalse
from warnings import warn
def add_aliases(obj):
"""Add aliases to the object.
Attributes beginning with an underscore are always skipped since they
should not be meant for external use.
A warning is issued if the new attribute already exists.
"""
attrs = dir(obj)
# Skip internal attributes.
def startswith(s):
return s.startswith('_')
attrs = ifilterfalse(startswith, attrs)
# Skip attributes compatible to PEP 8.
attrs = ifilterfalse(str.islower, attrs)
# Add attribute aliases.
for attr in attrs:
pep8_attr = str(AttributeName(attr))
if hasattr(obj, pep8_attr):
warn("Attribute '%s' exists, not replacing." % pep8_attr)
else:
setattr(obj, pep8_attr, getattr(obj, attr))
class AttributeName(object):
"""A name for a function, method or instance variable that obeys the
naming style suggested by :PEP:`8`.
"""
def __init__(self, name):
self.words = []
self.current_word = []
last_upper = False
uppercase_word = False
for char in name:
isupper = char.isupper()
if isupper:
if not last_upper:
self.push_word()
uppercase_word = last_upper
char = char.lower()
elif char == '_':
# An underscore indicates a word boundary.
self.push_word()
continue
elif uppercase_word and last_upper:
# An all-uppercase word was detected. The previous character
# already belongs to the next word, so transfer it.
prev_char = self.current_word.pop()
self.push_word()
self.current_word.append(prev_char)
last_upper = isupper
self.current_word.append(char)
self.push_word()
def push_word(self):
"""Push the current word into the words list."""
if self.current_word:
self.words.append(''.join(self.current_word))
self.current_word = []
def __str__(self):
return '_'.join(self.words)
# Tests to be executed with py.test_.
#
# .. _py.test: http://codespeak.net/py/dist/test.html
def test_add_aliases():
class X(object):
pass
x = X()
for attr in ('fooBar', 'FooBar'):
setattr(x, attr, None)
attr_len = len(dir(x))
# TODO: Assert warning?
add_aliases(x)
assert attr_len + 1 == len(dir(x))
def test_AttributeName():
translations = (
('lower', 'lower'),
('lower_with_underscores', 'lower_with_underscores'),
('Capitalized', 'capitalized'),
('CamelCase', 'camel_case'),
('mixedCase', 'mixed_case'),
('CamelCaseWithAllCAPS', 'camel_case_with_all_caps'),
('AllCAPSContainedSomething', 'all_caps_contained_something'),
('Capitalized_Words_With_Underscores',
'capitalized_words_with_underscores'),
)
for found_input, expected_output in translations:
assert str(AttributeName(found_input)) == expected_output