72
def _parse_source(source_text):
73
"""Get object to lineno mappings from given source_text"""
77
for node in ast.walk(ast.parse(source_text)):
78
# TODO: worry about duplicates?
79
if isinstance(node, ast.ClassDef):
80
# TODO: worry about nesting?
81
cls_to_lineno[node.name] = node.lineno
82
elif isinstance(node, ast.Str):
83
# Python AST gives location of string literal as the line the
84
# string terminates on. It's more useful to have the line the
85
# string begins on. Unfortunately, counting back newlines is
86
# only an approximation as the AST is ignorant of escaping.
87
str_to_lineno[node.s] = node.lineno - node.s.count('\n')
88
return cls_to_lineno, str_to_lineno
91
class _ModuleContext(object):
92
"""Record of the location within a source tree"""
94
def __init__(self, path, lineno=1, _source_info=None):
97
if _source_info is not None:
98
self._cls_to_lineno, self._str_to_lineno = _source_info
101
def from_module(cls, module):
102
"""Get new context from module object and parse source for linenos"""
103
sourcepath = inspect.getsourcefile(module)
104
# TODO: fix this to do the right thing rather than rely on cwd
105
relpath = os.path.relpath(sourcepath)
107
_source_info=_parse_source("".join(inspect.findsource(module)[0])))
109
def from_class(self, cls):
110
"""Get new context with same details but lineno of class in source"""
112
lineno = self._cls_to_lineno[cls.__name__]
113
except (AttributeError, KeyError):
114
mutter("Definition of %r not found in %r", cls, self.path)
116
return self.__class__(self.path, lineno,
117
(self._cls_to_lineno, self._str_to_lineno))
119
def from_string(self, string):
120
"""Get new context with same details but lineno of string in source"""
122
lineno = self._str_to_lineno[string]
123
except (AttributeError, KeyError):
124
mutter("String %r not found in %r", string[:20], self.path)
126
return self.__class__(self.path, lineno,
127
(self._cls_to_lineno, self._str_to_lineno))
71
130
class _PotExporter(object):
72
131
"""Write message details to output stream in .pot file format"""
74
133
def __init__(self, outf):
76
135
self._msgids = set()
136
self._module_contexts = {}
78
138
def poentry(self, path, lineno, s, comment=None):
79
139
if s in self._msgids:
101
165
self.poentry(path, lineno, p)
102
166
lineno += p.count('\n') + 2
105
_LAST_CACHE = _LAST_CACHED_SRC = None
107
def _offsets_of_literal(src):
108
global _LAST_CACHE, _LAST_CACHED_SRC
109
if src == _LAST_CACHED_SRC:
110
return _LAST_CACHE.copy()
113
root = ast.parse(src)
115
for node in ast.walk(root):
116
if not isinstance(node, ast.Str):
118
offsets[node.s] = node.lineno - node.s.count('\n')
120
_LAST_CACHED_SRC = src
121
_LAST_CACHE = offsets.copy()
168
def get_context(self, obj):
169
module = inspect.getmodule(obj)
171
context = self._module_contexts[module.__name__]
173
context = _ModuleContext.from_module(module)
174
self._module_contexts[module.__name__] = context
175
if inspect.isclass(obj):
176
context = context.from_class(obj)
180
def _write_option(exporter, context, opt, note):
181
if getattr(opt, 'hidden', False):
183
if getattr(opt, 'title', None):
184
exporter.poentry_in_context(context, opt.title,
185
"title of {name!r} {what}".format(name=opt.name, what=note))
186
if getattr(opt, 'help', None):
187
exporter.poentry_in_context(context, opt.help,
188
"help of {name!r} {what}".format(name=opt.name, what=note))
124
191
def _standard_options(exporter):
125
from bzrlib.option import Option
126
src = inspect.findsource(Option)[0]
128
path = 'bzrlib/option.py'
129
offsets = _offsets_of_literal(src)
131
for name in sorted(Option.OPTIONS.keys()):
132
opt = Option.OPTIONS[name]
133
if getattr(opt, 'hidden', False):
135
if getattr(opt, 'title', None):
136
lineno = offsets.get(opt.title, 9999)
138
note(gettext("%r is not found in bzrlib/option.py") % opt.title)
139
exporter.poentry(path, lineno, opt.title,
140
'title of %r option' % name)
141
if getattr(opt, 'help', None):
142
lineno = offsets.get(opt.help, 9999)
144
note(gettext("%r is not found in bzrlib/option.py") % opt.help)
145
exporter.poentry(path, lineno, opt.help,
146
'help of %r option' % name)
148
def _command_options(exporter, path, cmd):
149
src, default_lineno = inspect.findsource(cmd.__class__)
150
offsets = _offsets_of_literal(''.join(src))
192
OPTIONS = option.Option.OPTIONS
193
context = exporter.get_context(option)
194
for name in sorted(OPTIONS.keys()):
196
_write_option(exporter, context.from_string(name), opt, "option")
199
def _command_options(exporter, context, cmd):
200
note = "option of {0!r} command".format(cmd.name())
151
201
for opt in cmd.takes_options:
152
if isinstance(opt, str):
154
if getattr(opt, 'hidden', False):
157
if getattr(opt, 'title', None):
158
lineno = offsets.get(opt.title, default_lineno)
159
exporter.poentry(path, lineno, opt.title,
160
'title of %r option of %r command' % (name, cmd.name()))
161
if getattr(opt, 'help', None):
162
lineno = offsets.get(opt.help, default_lineno)
163
exporter.poentry(path, lineno, opt.help,
164
'help of %r option of %r command' % (name, cmd.name()))
202
# String values in Command option lists are for global options
203
if not isinstance(opt, str):
204
_write_option(exporter, context, opt, note)
167
207
def _write_command_help(exporter, cmd):
168
path = inspect.getfile(cmd.__class__)
169
if path.endswith('.pyc'):
171
path = os.path.relpath(path)
172
src, lineno = inspect.findsource(cmd.__class__)
173
offsets = _offsets_of_literal(''.join(src))
174
lineno = offsets[cmd.__doc__]
175
doc = inspect.getdoc(cmd)
208
context = exporter.get_context(cmd.__class__)
210
dcontext = context.from_string(rawdoc)
211
doc = inspect.cleandoc(rawdoc)
177
213
def exclude_usage(p):
178
214
# ':Usage:' has special meaning in help topics.