/
name_parser.py
150 lines (115 loc) · 4.97 KB
/
name_parser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# Copyright 2009-2011 Ram Rachum.
# This program is distributed under the LGPL2.1 license.
import abc
from python_toolbox import abc_tools
from python_toolbox import sequence_tools
from python_toolbox import string_tools
from python_toolbox.misc_tools import name_mangling
class CaseStyleType(abc.ABCMeta):
'''
A type of case style, dictating in what convention names should be written.
For example, `LowerCase` means names should be written 'like_this', while
`CamelCase` means that names should be written 'LikeThis'.
This is a metaclass; `LowerCase` and `CamelCase` are instances of this
class.
'''
class BaseCaseStyle(object):
'''Base class for case styles.'''
__metaclass__ = CaseStyleType
@abc_tools.AbstractStaticMethod
def parse(name):
'''
Parse a name with the given convention into a tuple of "words".
Returns `None` if there is no match.
'''
class LowerCase(BaseCaseStyle):
'''Naming style specifying that names should be written 'like_this'.'''
@staticmethod
def parse(name):
'''
Parse a name with the given convention into a tuple of "words".
For example, an input of 'on_navigation_panel__left_down' would result
in an output of `('navigation_panel', 'left_down')`.
Returns `None` if there is no match.
'''
if not name.startswith('on_'):
return None
cleaned_name = name[3:]
words = tuple(cleaned_name.split('__'))
return words
class CamelCase(BaseCaseStyle):
'''Naming style specifying that names should be written 'LikeThis'.'''
@staticmethod
def parse(name):
'''
Parse a name with the given convention into a tuple of "words".
For example, an input of 'OnNavigationPanel_LeftDown' would result in
an output of `('navigation_panel', 'left_down')`.
Returns `None` if there is no match.
'''
if not name.startswith('On'):
return None
cleaned_name = name[2:]
words = tuple(cleaned_name.split('_'))
return words
class NameParser(object):
'''
Parser that parses an event handler name.
For example, under default settings, '_on_navigation_panel__left_down' will
be parsed into a tuple `('navigation_panel', 'left_down')`.
'''
def __init__(self, case_style_possibilites=(LowerCase,),
n_preceding_underscores_possibilities=(1,)):
'''
Construct the `NameParser`.
In `case_style_possibilites` you may specify a set of case styles
(subclasses of `BaseCaseStyle`) that will be accepted by this parser.
In `n_preceding_underscores_possibilities`, you may specify a set of
ints signifying the number of underscores prefixing the name. For
example, if you specify `(1, 2)`, this parser will accept names
starting with either 1 or 2 underscores.
'''
self.case_style_possibilites = sequence_tools.to_tuple(
case_style_possibilites,
item_type=CaseStyleType
)
'''The set of case styles that this name parser accepts.'''
self.n_preceding_underscores_possibilities = sequence_tools.to_tuple(
n_preceding_underscores_possibilities
)
'''Set of number of preceding underscores that this parser accepts.'''
assert all(isinstance(case_style, CaseStyleType) for case_style in
self.case_style_possibilites)
assert all(isinstance(n_preceding_underscores, int) for
n_preceding_underscores in
self.n_preceding_underscores_possibilities)
def parse(self, name, class_name):
'''
Parse a name into a tuple of "words".
For example, under default settings, an input of
'_on_navigation_panel__left_down' would result in an output of
`('navigation_panel', 'left_down')`.
Returns `None` if there is no match.
'''
unmangled_name = name_mangling.unmangle_attribute_name_if_needed(
name,
class_name
)
n_preceding_underscores = string_tools.get_n_identical_edge_characters(
unmangled_name,
character='_',
head=True
)
if n_preceding_underscores not in \
self.n_preceding_underscores_possibilities:
return None
cleaned_name = unmangled_name[n_preceding_underscores:]
for case_style in self.case_style_possibilites:
result = case_style.parse(cleaned_name)
if result is not None:
return result
else:
return None
def match(self, name, class_name):
'''Does `name` match our parser? (i.e. can it be parsed into words?)'''
return (self.parse(name, class_name) is not None)