pyflyby 1.9.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyflyby might be problematic. Click here for more details.

Files changed (54) hide show
  1. pyflyby/__init__.py +56 -0
  2. pyflyby/__main__.py +9 -0
  3. pyflyby/_autoimp.py +2114 -0
  4. pyflyby/_cmdline.py +531 -0
  5. pyflyby/_comms.py +221 -0
  6. pyflyby/_dbg.py +1339 -0
  7. pyflyby/_docxref.py +379 -0
  8. pyflyby/_file.py +738 -0
  9. pyflyby/_flags.py +230 -0
  10. pyflyby/_format.py +182 -0
  11. pyflyby/_idents.py +233 -0
  12. pyflyby/_import_sorting.py +165 -0
  13. pyflyby/_importclns.py +642 -0
  14. pyflyby/_importdb.py +588 -0
  15. pyflyby/_imports2s.py +639 -0
  16. pyflyby/_importstmt.py +662 -0
  17. pyflyby/_interactive.py +2605 -0
  18. pyflyby/_livepatch.py +793 -0
  19. pyflyby/_log.py +199 -0
  20. pyflyby/_modules.py +515 -0
  21. pyflyby/_parse.py +1441 -0
  22. pyflyby/_py.py +2078 -0
  23. pyflyby/_util.py +459 -0
  24. pyflyby/_version.py +7 -0
  25. pyflyby/autoimport.py +20 -0
  26. pyflyby/importdb.py +19 -0
  27. pyflyby-1.9.4.data/data/etc/pyflyby/canonical.py +10 -0
  28. pyflyby-1.9.4.data/data/etc/pyflyby/common.py +27 -0
  29. pyflyby-1.9.4.data/data/etc/pyflyby/forget.py +10 -0
  30. pyflyby-1.9.4.data/data/etc/pyflyby/mandatory.py +10 -0
  31. pyflyby-1.9.4.data/data/etc/pyflyby/numpy.py +156 -0
  32. pyflyby-1.9.4.data/data/etc/pyflyby/std.py +335 -0
  33. pyflyby-1.9.4.data/data/libexec/pyflyby/colordiff +34 -0
  34. pyflyby-1.9.4.data/data/libexec/pyflyby/diff-colorize +148 -0
  35. pyflyby-1.9.4.data/data/share/doc/pyflyby/LICENSE.txt +23 -0
  36. pyflyby-1.9.4.data/data/share/doc/pyflyby/TODO.txt +115 -0
  37. pyflyby-1.9.4.data/data/share/doc/pyflyby/testing.txt +13 -0
  38. pyflyby-1.9.4.data/data/share/emacs/site-lisp/pyflyby.el +108 -0
  39. pyflyby-1.9.4.data/scripts/collect-exports +76 -0
  40. pyflyby-1.9.4.data/scripts/collect-imports +58 -0
  41. pyflyby-1.9.4.data/scripts/find-import +38 -0
  42. pyflyby-1.9.4.data/scripts/list-bad-xrefs +34 -0
  43. pyflyby-1.9.4.data/scripts/prune-broken-imports +34 -0
  44. pyflyby-1.9.4.data/scripts/pyflyby-diff +34 -0
  45. pyflyby-1.9.4.data/scripts/reformat-imports +27 -0
  46. pyflyby-1.9.4.data/scripts/replace-star-imports +37 -0
  47. pyflyby-1.9.4.data/scripts/tidy-imports +191 -0
  48. pyflyby-1.9.4.data/scripts/transform-imports +47 -0
  49. pyflyby-1.9.4.dist-info/LICENSE.txt +23 -0
  50. pyflyby-1.9.4.dist-info/METADATA +507 -0
  51. pyflyby-1.9.4.dist-info/RECORD +54 -0
  52. pyflyby-1.9.4.dist-info/WHEEL +5 -0
  53. pyflyby-1.9.4.dist-info/entry_points.txt +3 -0
  54. pyflyby-1.9.4.dist-info/top_level.txt +1 -0
pyflyby/_flags.py ADDED
@@ -0,0 +1,230 @@
1
+ # pyflyby/_flags.py.
2
+ # Copyright (C) 2011, 2012, 2013, 2014 Karl Chen.
3
+ # License: MIT http://opensource.org/licenses/MIT
4
+
5
+
6
+
7
+ import __future__
8
+ import ast
9
+ import operator
10
+ from functools import reduce
11
+ import warnings
12
+
13
+ from pyflyby._util import cached_attribute
14
+ from typing import Tuple
15
+
16
+ # Initialize mappings from compiler_flag to feature name and vice versa.
17
+ _FLAG2NAME = {}
18
+ _NAME2FLAG = {}
19
+ for name in __future__.all_feature_names:
20
+ flag = getattr(__future__, name).compiler_flag
21
+ _FLAG2NAME[flag] = name
22
+ _NAME2FLAG[name] = flag
23
+ for name in dir(ast):
24
+ if name.startswith('PyCF'):
25
+ flag_name = name[len('PyCF_'):].lower()
26
+ flag = getattr(ast, name)
27
+ _FLAG2NAME[flag] = flag_name
28
+ _NAME2FLAG[flag_name] = flag
29
+ _FLAGNAME_ITEMS = sorted(_FLAG2NAME.items())
30
+ _ALL_FLAGS = reduce(operator.or_, _FLAG2NAME.keys())
31
+
32
+
33
+
34
+ class CompilerFlags(int):
35
+ """
36
+ Representation of Python "compiler flags", i.e. features from __future__.
37
+
38
+ >>> print(CompilerFlags(0x18000).__interactive_display__()) # doctest: +SKIP
39
+ CompilerFlags(0x18000) # from __future__ import with_statement, print_function
40
+
41
+ >>> print(CompilerFlags(0x10000, 0x8000).__interactive_display__()) # doctest: +SKIP
42
+ CompilerFlags(0x18000) # from __future__ import with_statement, print_function
43
+
44
+ >>> print(CompilerFlags('with_statement', 'print_function').__interactive_display__()) # doctest: +SKIP
45
+ CompilerFlags(0x18000) # from __future__ import with_statement, print_function
46
+
47
+ >>> compile("print('x', file=None)", "?", "exec", flags=CompilerFlags("print_function"), dont_inherit=1) #doctest:+ELLIPSIS
48
+ <code object ...>
49
+
50
+ """
51
+
52
+ # technically both those are compiler flags, but we can't use Self. May need typing_extensions ?
53
+ _ZERO:int
54
+ _UNKNOWN: int
55
+
56
+ def __new__(cls, *args):
57
+ """
58
+ Construct a new ``CompilerFlags`` instance.
59
+
60
+ :param args:
61
+ Any number (zero or more) ``CompilerFlags`` s, ``int`` s, or ``str`` s,
62
+ which are bitwise-ORed together.
63
+ :rtype:
64
+ `CompilerFlags`
65
+ """
66
+ if len(args) == 0:
67
+ return cls._ZERO
68
+ elif len(args) == 1:
69
+ arg, = args
70
+ if isinstance(arg, cls):
71
+ return arg
72
+ elif arg is None:
73
+ return cls._ZERO
74
+ elif isinstance(arg, int):
75
+ warnings.warn('creating CompilerFlags from integers is deprecated, '
76
+ ' flags values change between Python versions. If you are sure use .from_int',
77
+ DeprecationWarning, stacklevel=2)
78
+ return cls.from_int(arg)
79
+ elif isinstance(arg, str):
80
+ return cls.from_str(arg)
81
+ elif isinstance(arg, ast.AST):
82
+ return cls.from_ast(arg)
83
+ elif isinstance(arg, (tuple, list)):
84
+ return cls(*arg)
85
+ else:
86
+ raise TypeError("CompilerFlags: unknown type %s"
87
+ % (type(arg).__name__,))
88
+ else:
89
+ flags = []
90
+ for x in args:
91
+ if isinstance(x, cls):
92
+ flags.append(int(x))
93
+ elif isinstance(x, int):
94
+ warnings.warn(
95
+ "creating CompilerFlags from integers is deprecated, "
96
+ " flags values change between Python versions. If you are sure use .from_int",
97
+ DeprecationWarning,
98
+ stacklevel=2,
99
+ )
100
+ flags.append(x)
101
+ elif isinstance(x, str):
102
+ flags.append(int(cls(x)))
103
+ else:
104
+ raise ValueError
105
+
106
+ #assert flags == [0x10000, 0x8000], flags
107
+
108
+ return cls.from_int(reduce(operator.or_, flags))
109
+
110
+ @classmethod
111
+ def from_int(cls, arg):
112
+ if arg == -1:
113
+ return cls._UNKNOWN # Instance optimization
114
+ if arg == 0:
115
+ return cls._ZERO # Instance optimization
116
+ self = int.__new__(cls, arg)
117
+ bad_flags = int(self) & ~_ALL_FLAGS
118
+ if bad_flags:
119
+ raise ValueError(
120
+ "CompilerFlags: unknown flag value(s) %s %s" % (bin(bad_flags), hex(bad_flags)))
121
+ return self
122
+
123
+ @classmethod
124
+ def from_str(cls, arg:str):
125
+ try:
126
+ flag = _NAME2FLAG[arg]
127
+ except KeyError:
128
+ raise ValueError(
129
+ "CompilerFlags: unknown flag %r" % (arg,))
130
+ return cls.from_int(flag)
131
+
132
+ @classmethod
133
+ def from_ast(cls, nodes):
134
+ """
135
+ Parse the compiler flags from AST node(s).
136
+
137
+ :type nodes:
138
+ ``ast.AST`` or sequence thereof
139
+ :rtype:
140
+ ``CompilerFlags``
141
+ """
142
+ if isinstance(nodes, ast.Module):
143
+ nodes = nodes.body
144
+ elif isinstance(nodes, ast.AST):
145
+ nodes = [nodes]
146
+ flags = []
147
+ for node in nodes:
148
+ if not isinstance(node, ast.ImportFrom):
149
+ # Got a non-import; stop looking further.
150
+ break
151
+ if not node.module == "__future__":
152
+ # Got a non-__future__-import; stop looking further.
153
+ break
154
+ # Get the feature names.
155
+ names = [n.name for n in node.names]
156
+ flags.extend(names)
157
+ return cls(flags)
158
+
159
+ @cached_attribute
160
+ def names(self) -> Tuple[str, ...]:
161
+ return tuple(
162
+ n
163
+ for f, n in _FLAGNAME_ITEMS
164
+ if f & self)
165
+
166
+ def __or__(self, o):
167
+ if o == 0:
168
+ return self
169
+ if not isinstance(o, CompilerFlags):
170
+ o = CompilerFlags(o)
171
+ if self == 0:
172
+ return o
173
+ return CompilerFlags.from_int(int(self) | int(o))
174
+
175
+ def __ror__(self, o):
176
+ return self | o
177
+
178
+ def __and__(self, o):
179
+ if not isinstance(o, int):
180
+ o = CompilerFlags(o)
181
+ return CompilerFlags.from_int(int(self) & int(o))
182
+
183
+ def __rand__(self, o):
184
+ return self & o
185
+
186
+ def __xor__(self, o):
187
+ if not isinstance(o, CompilerFlags):
188
+ o = CompilerFlags.from_int(o)
189
+ return CompilerFlags.from_int(int(self) ^ int(o))
190
+
191
+ def __rxor__(self, o):
192
+ return self ^ o
193
+
194
+ def __repr__(self):
195
+ return "CompilerFlags(%s)" % (hex(self),)
196
+
197
+ def __str__(self):
198
+ return hex(self)
199
+
200
+ def __interactive_display__(self):
201
+ s = repr(self)
202
+ if self != 0:
203
+ s += " # from __future__ import " + ", ".join(self.names)
204
+ return s
205
+
206
+
207
+ CompilerFlags._ZERO = int.__new__(CompilerFlags, 0)
208
+ CompilerFlags._UNKNOWN = int.__new__(CompilerFlags, -1)
209
+
210
+ # flags that _may_ exists on future versions.
211
+ _future_flags = {
212
+ "nested_scopes",
213
+ "generators",
214
+ "division",
215
+ "absolute_import",
216
+ "with_statement",
217
+ "print_function",
218
+ "unicode_literals",
219
+ "barry_as_FLUFL",
220
+ "generator_stop",
221
+ "annotations",
222
+ "allow_top_level_await",
223
+ "only_ast",
224
+ "type_comments",
225
+ }
226
+ for k in _future_flags:
227
+ setattr(CompilerFlags, k, CompilerFlags._UNKNOWN)
228
+
229
+ for k, v in _NAME2FLAG.items():
230
+ setattr(CompilerFlags, k, CompilerFlags.from_int(v))
pyflyby/_format.py ADDED
@@ -0,0 +1,182 @@
1
+ # pyflyby/_format.py.
2
+ # Copyright (C) 2011, 2012, 2013, 2014 Karl Chen.
3
+ # License: MIT http://opensource.org/licenses/MIT
4
+
5
+
6
+
7
+ class FormatParams(object):
8
+ max_line_length = None
9
+ _max_line_lenght_default = 79
10
+ wrap_paren = True
11
+ indent = 4
12
+ hanging_indent = 'never'
13
+ use_black = False
14
+
15
+ def __new__(cls, *args, **kwargs):
16
+ if not kwargs and len(args) == 1 and isinstance(args[0], cls):
17
+ return args[0]
18
+ self = object.__new__(cls)
19
+ # TODO: be more careful here
20
+ dicts = []
21
+ for arg in args:
22
+ if arg is None:
23
+ pass
24
+ elif isinstance(arg, cls) or hasattr(self, "__dict__"):
25
+ dicts.append(arg.__dict__)
26
+ else:
27
+ raise TypeError(
28
+ "expected None, or instance of %s cls, got %s" % (cls, arg)
29
+ )
30
+ if kwargs:
31
+ dicts.append(kwargs)
32
+ for kwargs in dicts:
33
+ for key, value in kwargs.items():
34
+ if hasattr(self, key):
35
+ setattr(self, key, value)
36
+ else:
37
+ raise ValueError("bad kwarg %r" % (key,))
38
+ return self
39
+
40
+ def __repr__(self):
41
+ return f'<{self.__class__.__name__} {self.__dict__}>'
42
+
43
+
44
+ def fill(tokens, sep=(", ", ""), prefix="", suffix="", newline="\n",
45
+ max_line_length=80):
46
+ r"""
47
+ Given a sequences of strings, fill them into a single string with up to
48
+ ``max_line_length`` characters each.
49
+
50
+ >>> fill(["'hello world'", "'hello two'"],
51
+ ... prefix=("print ", " "), suffix=(" \\", ""),
52
+ ... max_line_length=25)
53
+ "print 'hello world', \\\n 'hello two'\n"
54
+
55
+ :param tokens:
56
+ Sequence of strings to fill. There must be at least one token.
57
+ :param sep:
58
+ Separator string to append to each token. If a 2-element tuple, then
59
+ indicates the separator between tokens and the separator after the last
60
+ token. Trailing whitespace is removed from each line before appending
61
+ the suffix, but not from between tokens on the same line.
62
+ :param prefix:
63
+ String to prepend at the beginning of each line. If a 2-element tuple,
64
+ then indicates the prefix for the first line and prefix for subsequent
65
+ lines.
66
+ :param suffix:
67
+ String to append to the end of each line. If a 2-element tuple, then
68
+ indicates the suffix for all lines except the last, and the suffix for
69
+ the last line.
70
+ :return:
71
+ Filled string.
72
+ """
73
+ N = max_line_length
74
+ assert len(tokens) > 0
75
+ if isinstance(prefix, tuple):
76
+ first_prefix, cont_prefix = prefix
77
+ else:
78
+ first_prefix = cont_prefix = prefix
79
+ if isinstance(suffix, tuple):
80
+ nonterm_suffix, term_suffix = suffix
81
+ else:
82
+ nonterm_suffix = term_suffix = suffix
83
+ if isinstance(sep, tuple):
84
+ nonterm_sep, term_sep = sep
85
+ else:
86
+ nonterm_sep = term_sep = sep
87
+ lines = [first_prefix + tokens[0]]
88
+ for token, is_last in zip(tokens[1:], [False]*(len(tokens)-2) + [True]):
89
+ suffix = term_suffix if is_last else nonterm_suffix
90
+ sep = (term_sep if is_last else nonterm_sep).rstrip()
91
+ # Does the next token fit?
92
+ if len(lines[-1] + nonterm_sep + token + sep + suffix) <= N:
93
+ # Yes; add it.
94
+ lines[-1] += nonterm_sep + token
95
+ else:
96
+ # No; break into new line.
97
+ lines[-1] += nonterm_sep.rstrip() + nonterm_suffix + newline
98
+ lines.append(cont_prefix + token)
99
+ lines[-1] += term_sep.rstrip() + term_suffix + newline
100
+ return ''.join(lines)
101
+
102
+
103
+ def pyfill(prefix, tokens, params=FormatParams()):
104
+ """
105
+ Fill a Python statement.
106
+
107
+ >>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"]), end='')
108
+ print foo.bar, baz, quux, quuuuux
109
+ >>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"],
110
+ ... FormatParams(max_line_length=15, hanging_indent='auto')), end='')
111
+ print (foo.bar,
112
+ baz,
113
+ quux,
114
+ quuuuux)
115
+ >>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"],
116
+ ... FormatParams(max_line_length=14, hanging_indent='auto')), end='')
117
+ print (
118
+ foo.bar,
119
+ baz, quux,
120
+ quuuuux)
121
+
122
+ :param prefix:
123
+ Prefix for first line.
124
+ :param tokens:
125
+ Sequence of string tokens
126
+ :type params:
127
+ `FormatParams`
128
+ :rtype:
129
+ ``str``
130
+ """
131
+ N = params.max_line_length or params._max_line_lenght_default
132
+ if params.wrap_paren:
133
+ # Check how we will break up the tokens.
134
+ len_full = sum(len(tok) for tok in tokens) + 2 * (len(tokens)-1)
135
+ if len(prefix) + len_full <= N:
136
+ # The entire thing fits on one line; no parens needed. We check
137
+ # this first because breaking into lines adds paren overhead.
138
+ #
139
+ # Output looks like:
140
+ # from foo import abc, defgh, ijkl, mnopq, rst
141
+ return prefix + ", ".join(tokens) + "\n"
142
+ if params.hanging_indent == "never":
143
+ hanging_indent = False
144
+ elif params.hanging_indent == "always":
145
+ hanging_indent = True
146
+ elif params.hanging_indent == "auto":
147
+ # Decide automatically whether to do hanging-indent mode. If any
148
+ # line would exceed the max_line_length, then do hanging indent;
149
+ # else don't.
150
+ #
151
+ # In order to use non-hanging-indent mode, the first line would
152
+ # have an overhead of 2 because of "(" and ",". We check the
153
+ # longest token since even if the first token fits, we still want
154
+ # to avoid later tokens running over N.
155
+ maxtoklen = max(len(token) for token in tokens)
156
+ hanging_indent = (len(prefix) + maxtoklen + 2 > N)
157
+ else:
158
+ raise ValueError("bad params.hanging_indent=%r"
159
+ % (params.hanging_indent,))
160
+ if hanging_indent:
161
+ # Hanging indent mode. We need a single opening paren and
162
+ # continue all imports on separate lines.
163
+ #
164
+ # Output looks like:
165
+ # from foo import (
166
+ # abc, defgh, ijkl,
167
+ # mnopq, rst)
168
+ return (prefix + "(\n"
169
+ + fill(tokens, max_line_length=N,
170
+ prefix=(" " * params.indent), suffix=("", ")")))
171
+ else:
172
+ # Non-hanging-indent mode.
173
+ #
174
+ # Output looks like:
175
+ # from foo import (abc, defgh,
176
+ # ijkl, mnopq,
177
+ # rst)
178
+ pprefix = prefix + "("
179
+ return fill(tokens, max_line_length=N,
180
+ prefix=(pprefix, " " * len(pprefix)), suffix=("", ")"))
181
+ else:
182
+ raise NotImplementedError
pyflyby/_idents.py ADDED
@@ -0,0 +1,233 @@
1
+ # pyflyby/_idents.py.
2
+ # Copyright (C) 2011, 2012, 2013, 2014, 2018 Karl Chen.
3
+ # License: MIT http://opensource.org/licenses/MIT
4
+
5
+
6
+
7
+ from functools import total_ordering
8
+ from keyword import kwlist
9
+ import re
10
+
11
+ from pyflyby._util import cached_attribute, cmp
12
+
13
+ from typing import Optional, Tuple, Dict
14
+
15
+
16
+ # Don't consider "print" a keyword, in order to be compatible with user code
17
+ # that uses "from __future__ import print_function".
18
+ _my_kwlist = list(kwlist)
19
+ _my_iskeyword = frozenset(_my_kwlist).__contains__
20
+
21
+
22
+ # TODO: use DottedIdentifier.prefixes
23
+ def dotted_prefixes(dotted_name, reverse=False):
24
+ """
25
+ Return the prefixes of a dotted name.
26
+
27
+ >>> dotted_prefixes("aa.bb.cc")
28
+ ['aa', 'aa.bb', 'aa.bb.cc']
29
+
30
+ >>> dotted_prefixes("aa.bb.cc", reverse=True)
31
+ ['aa.bb.cc', 'aa.bb', 'aa']
32
+
33
+ :type dotted_name:
34
+ ``str``
35
+ :param reverse:
36
+ If False (default), return shortest to longest. If True, return longest
37
+ to shortest.
38
+ :rtype:
39
+ ``list`` of ``str``
40
+ """
41
+ name_parts = dotted_name.split(".")
42
+ if reverse:
43
+ idxes = range(len(name_parts), 0, -1)
44
+ else:
45
+ idxes = range(1, len(name_parts)+1)
46
+ result = ['.'.join(name_parts[:i]) or '.' for i in idxes]
47
+ return result
48
+
49
+
50
+ def is_identifier(s: str, dotted: bool = False, prefix: bool = False):
51
+ """
52
+ Return whether ``s`` is a valid Python identifier name.
53
+
54
+ >>> is_identifier("foo")
55
+ True
56
+
57
+ >>> is_identifier("foo+bar")
58
+ False
59
+
60
+ >>> is_identifier("from")
61
+ False
62
+
63
+ By default, we check whether ``s`` is a single valid identifier, meaning
64
+ dots are not allowed. If ``dotted=True``, then we check each dotted
65
+ component::
66
+
67
+ >>> is_identifier("foo.bar")
68
+ False
69
+
70
+ >>> is_identifier("foo.bar", dotted=True)
71
+ True
72
+
73
+ >>> is_identifier("foo..bar", dotted=True)
74
+ False
75
+
76
+ >>> is_identifier("foo.from", dotted=True)
77
+ False
78
+
79
+ By default, the string must comprise a valid identifier. If
80
+ ``prefix=True``, then allow strings that are prefixes of valid identifiers.
81
+ Prefix=False excludes the empty string, strings with a trailing dot, and
82
+ strings with a trailing keyword component, but prefix=True does not
83
+ exclude these.
84
+
85
+ >>> is_identifier("foo.bar.", dotted=True)
86
+ False
87
+
88
+ >>> is_identifier("foo.bar.", dotted=True, prefix=True)
89
+ True
90
+
91
+ >>> is_identifier("foo.or", dotted=True)
92
+ False
93
+
94
+ >>> is_identifier("foo.or", dotted=True, prefix=True)
95
+ True
96
+
97
+ :type s:
98
+ ``str``
99
+ :param dotted:
100
+ If ``False`` (default), then the input must be a single name such as
101
+ "foo". If ``True``, then the input can be a single name or a dotted name
102
+ such as "foo.bar.baz".
103
+ :param prefix:
104
+ If ``False`` (Default), then the input must be a valid identifier. If
105
+ ``True``, then the input can be a valid identifier or the prefix of a
106
+ valid identifier.
107
+ :rtype:
108
+ ``bool``
109
+ """
110
+ if not isinstance(s, str):
111
+ raise TypeError("is_identifier(): expected a string; got a %s"
112
+ % (type(s).__name__,))
113
+ if prefix:
114
+ return is_identifier(s + '_', dotted=dotted, prefix=False)
115
+ if dotted:
116
+ return all(is_identifier(w, dotted=False) for w in s.split('.'))
117
+ return s.isidentifier() and not _my_iskeyword(s)
118
+
119
+
120
+ def brace_identifiers(text):
121
+ """
122
+ Parse a string and yield all tokens of the form "{some_token}".
123
+
124
+ >>> list(brace_identifiers("{salutation}, {your_name}."))
125
+ ['salutation', 'your_name']
126
+ """
127
+ if isinstance(text, bytes):
128
+ text = text.decode('utf-8', errors='replace')
129
+ for match in re.finditer("{([a-zA-Z_][a-zA-Z0-9_]*)}", text):
130
+ yield match.group(1)
131
+
132
+
133
+ class BadDottedIdentifierError(ValueError):
134
+ pass
135
+
136
+
137
+ # TODO: Use in various places, esp where e.g. dotted_prefixes is used.
138
+ @total_ordering
139
+ class DottedIdentifier:
140
+ name: str
141
+ parts: Tuple[str, ...]
142
+ scope_info: Optional[Dict]
143
+
144
+ def __new__(cls, arg, scope_info=None):
145
+ if isinstance(arg, cls):
146
+ return arg
147
+ if isinstance(arg, str):
148
+ return cls._from_name(arg, scope_info)
149
+ if isinstance(arg, (tuple, list)):
150
+ return cls._from_name(".".join(arg), scope_info)
151
+ raise TypeError("DottedIdentifier: unexpected %s"
152
+ % (type(arg).__name__,))
153
+
154
+ @classmethod
155
+ def _from_name(cls, name, scope_info=None):
156
+ self = object.__new__(cls)
157
+ self.name = str(name)
158
+ # TODO: change magic methods to compare with scopestack included
159
+ self.scope_info = scope_info
160
+ if not is_identifier(self.name, dotted=True):
161
+ if len(self.name) > 20:
162
+ raise BadDottedIdentifierError("Invalid python symbol name")
163
+ else:
164
+ raise BadDottedIdentifierError("Invalid python symbol name %r"
165
+ % (name,))
166
+ self.parts = tuple(self.name.split('.'))
167
+ return self
168
+
169
+ @cached_attribute
170
+ def parent(self):
171
+ if len(self.parts) > 1:
172
+ return DottedIdentifier('.'.join(self.parts[:-1]))
173
+ else:
174
+ return None
175
+
176
+ @cached_attribute
177
+ def prefixes(self):
178
+ parts = self.parts
179
+ idxes = range(1, len(parts)+1)
180
+ result = ['.'.join(parts[:i]) for i in idxes]
181
+ return tuple(DottedIdentifier(x) for x in result)
182
+
183
+ def startswith(self, o):
184
+ o = type(self)(o)
185
+ return self.parts[:len(o.parts)] == o.parts
186
+
187
+ def __getitem__(self, x):
188
+ return type(self)(self.parts[x])
189
+
190
+ def __len__(self):
191
+ return len(self.parts)
192
+
193
+ def __iter__(self):
194
+ return (type(self)(x) for x in self.parts)
195
+
196
+ def __add__(self, suffix):
197
+ return type(self)("%s.%s" % (self, suffix))
198
+
199
+ def __str__(self):
200
+ return self.name
201
+
202
+ def __repr__(self):
203
+ return "%s(%r)" % (type(self).__name__, self.name)
204
+
205
+ def __hash__(self):
206
+ return hash(self.name)
207
+
208
+ def __eq__(self, other):
209
+ if self is other:
210
+ return True
211
+ if not isinstance(other, DottedIdentifier):
212
+ return NotImplemented
213
+ return self.name == other.name
214
+
215
+ def __ne__(self, other):
216
+ if self is other:
217
+ return False
218
+ if not isinstance(other, DottedIdentifier):
219
+ return NotImplemented
220
+ return self.name != other.name
221
+
222
+ # The rest are defined by total_ordering
223
+ def __lt__(self, other):
224
+ if not isinstance(other, DottedIdentifier):
225
+ return NotImplemented
226
+ return self.name < other.name
227
+
228
+ def __cmp__(self, other):
229
+ if self is other:
230
+ return 0
231
+ if not isinstance(other, DottedIdentifier):
232
+ return NotImplemented
233
+ return cmp(self.name, other.name)