/usr/share/pyshared/epydoc/docstringparser.py is in python-epydoc 3.0.1+dfsg-5.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 | # epydoc -- Docstring processing
#
# Copyright (C) 2005 Edward Loper
# Author: Edward Loper <edloper@loper.org>
# URL: <http://epydoc.sf.net>
#
# $Id: docstringparser.py 1689 2008-01-30 17:01:02Z edloper $
"""
Parse docstrings and handle any fields it defines, such as C{@type}
and C{@author}. Fields are used to describe specific information
about an object. There are two classes of fields: X{simple fields}
and X{special fields}.
Simple fields are fields that get stored directly in an C{APIDoc}'s
metadata dictionary, without any special processing. The set of
simple fields is defined by the list L{STANDARD_FIELDS}, whose
elements are L{DocstringField}s.
Special fields are fields that perform some sort of processing on the
C{APIDoc}, or add information to attributes other than the metadata
dictionary. Special fields are are handled by field handler
functions, which are registered using L{register_field_handler}.
"""
__docformat__ = 'epytext en'
######################################################################
## Imports
######################################################################
import re, sys
from epydoc import markup
from epydoc.markup import epytext
from epydoc.apidoc import *
from epydoc.docintrospecter import introspect_docstring_lineno
from epydoc.util import py_src_filename
from epydoc import log
import epydoc.docparser
import __builtin__, exceptions
######################################################################
# Docstring Fields
######################################################################
class DocstringField:
"""
A simple docstring field, which can be used to describe specific
information about an object, such as its author or its version.
Simple docstring fields are fields that take no arguments, and
are displayed as simple sections.
@ivar tags: The set of tags that can be used to identify this
field.
@ivar singular: The label that should be used to identify this
field in the output, if the field contains one value.
@ivar plural: The label that should be used to identify this
field in the output, if the field contains multiple values.
@ivar short: If true, then multiple values should be combined
into a single comma-delimited list. If false, then
multiple values should be listed separately in a bulleted
list.
@ivar multivalue: If true, then multiple values may be given
for this field; if false, then this field can only take a
single value, and a warning should be issued if it is
redefined.
@ivar takes_arg: If true, then this field expects an argument;
and a separate field section will be constructed for each
argument value. The label (and plural label) should include
a '%s' to mark where the argument's string rep should be
added.
"""
def __init__(self, tags, label, plural=None,
short=0, multivalue=1, takes_arg=0,
varnames=None):
if type(tags) in (list, tuple):
self.tags = tuple(tags)
elif type(tags) is str:
self.tags = (tags,)
else: raise TypeError('Bad tags: %s' % tags)
self.singular = label
if plural is None: self.plural = label
else: self.plural = plural
self.multivalue = multivalue
self.short = short
self.takes_arg = takes_arg
self.varnames = varnames or []
def __cmp__(self, other):
if not isinstance(other, DocstringField): return -1
return cmp(self.tags, other.tags)
def __hash__(self):
return hash(self.tags)
def __repr__(self):
return '<Field: %s>' % self.tags[0]
STANDARD_FIELDS = [
#: A list of the standard simple fields accepted by epydoc. This
#: list can be augmented at run-time by a docstring with the special
#: C{@deffield} field. The order in which fields are listed here
#: determines the order in which they will be displayed in the
#: output.
# If it's deprecated, put that first.
DocstringField(['deprecated', 'depreciated'],
'Deprecated', multivalue=0, varnames=['__deprecated__']),
# Status info
DocstringField(['version'], 'Version', multivalue=0,
varnames=['__version__']),
DocstringField(['date'], 'Date', multivalue=0,
varnames=['__date__']),
DocstringField(['status'], 'Status', multivalue=0),
# Bibliographic Info
DocstringField(['author', 'authors'], 'Author', 'Authors', short=1,
varnames=['__author__', '__authors__']),
DocstringField(['contact'], 'Contact', 'Contacts', short=1,
varnames=['__contact__']),
DocstringField(['organization', 'org'],
'Organization', 'Organizations'),
DocstringField(['copyright', '(c)'], 'Copyright', multivalue=0,
varnames=['__copyright__']),
DocstringField(['license'], 'License', multivalue=0,
varnames=['__license__']),
# Various warnings etc.
DocstringField(['bug'], 'Bug', 'Bugs'),
DocstringField(['warning', 'warn'], 'Warning', 'Warnings'),
DocstringField(['attention'], 'Attention'),
DocstringField(['note'], 'Note', 'Notes'),
# Formal conditions
DocstringField(['requires', 'require', 'requirement'], 'Requires'),
DocstringField(['precondition', 'precond'],
'Precondition', 'Preconditions'),
DocstringField(['postcondition', 'postcond'],
'Postcondition', 'Postconditions'),
DocstringField(['invariant'], 'Invariant'),
# When was it introduced (version # or date)
DocstringField(['since'], 'Since', multivalue=0),
# Changes made
DocstringField(['change', 'changed'], 'Change Log'),
# Crossreferences
DocstringField(['see', 'seealso'], 'See Also', short=1),
# Future Work
DocstringField(['todo'], 'To Do', takes_arg=True),
# Permissions (used by zope-based projects)
DocstringField(['permission', 'permissions'], 'Permission', 'Permissions')
]
######################################################################
#{ Docstring Parsing
######################################################################
DEFAULT_DOCFORMAT = 'epytext'
"""The name of the default markup languge used to process docstrings."""
# [xx] keep track of which ones we've already done, in case we're
# asked to process one twice? e.g., for @include we might have to
# parse the included docstring earlier than we might otherwise..??
def parse_docstring(api_doc, docindex, suppress_warnings=[]):
"""
Process the given C{APIDoc}'s docstring. In particular, populate
the C{APIDoc}'s C{descr} and C{summary} attributes, and add any
information provided by fields in the docstring.
@param docindex: A DocIndex, used to find the containing
module (to look up the docformat); and to find any
user docfields defined by containing objects.
@param suppress_warnings: A set of objects for which docstring
warnings should be suppressed.
"""
if api_doc.metadata is not UNKNOWN:
if not (isinstance(api_doc, RoutineDoc)
and api_doc.canonical_name[-1] == '__init__'):
log.debug("%s's docstring processed twice" %
api_doc.canonical_name)
return
initialize_api_doc(api_doc)
# If there's no docstring, then check for special variables (e.g.,
# __version__), and then return -- there's nothing else to do.
if (api_doc.docstring in (None, UNKNOWN)):
if isinstance(api_doc, NamespaceDoc):
for field in STANDARD_FIELDS + user_docfields(api_doc, docindex):
add_metadata_from_var(api_doc, field)
return
# Remove leading indentation from the docstring.
api_doc.docstring = unindent_docstring(api_doc.docstring)
# Decide which docformat is used by this module.
docformat = get_docformat(api_doc, docindex)
# A list of markup errors from parsing.
parse_errors = []
# Extract a signature from the docstring, if it has one. This
# overrides any signature we got via introspection/parsing.
if isinstance(api_doc, RoutineDoc):
parse_function_signature(api_doc, None, docformat, parse_errors)
# Parse the docstring. Any errors encountered are stored as
# `ParseError` objects in the errors list.
parsed_docstring = markup.parse(api_doc.docstring, docformat,
parse_errors)
# Divide the docstring into a description and a list of
# fields.
descr, fields = parsed_docstring.split_fields(parse_errors)
api_doc.descr = descr
field_warnings = []
# Handle the constructor fields that have been defined in the class
# docstring. This code assumes that a class docstring is parsed before
# the same class __init__ docstring.
if isinstance(api_doc, ClassDoc):
# Parse ahead the __init__ docstring for this class
initvar = api_doc.variables.get('__init__')
if initvar and isinstance(initvar.value, RoutineDoc):
init_api_doc = initvar.value
parse_docstring(init_api_doc, docindex, suppress_warnings)
parse_function_signature(init_api_doc, api_doc,
docformat, parse_errors)
init_fields = split_init_fields(fields, field_warnings)
# Process fields
for field in init_fields:
try:
process_field(init_api_doc, docindex, field.tag(),
field.arg(), field.body())
except ValueError, e: field_warnings.append(str(e))
# Process fields
for field in fields:
try:
process_field(api_doc, docindex, field.tag(),
field.arg(), field.body())
except ValueError, e: field_warnings.append(str(e))
# Check to make sure that all type parameters correspond to
# some documented parameter.
check_type_fields(api_doc, field_warnings)
# Check for special variables (e.g., __version__)
if isinstance(api_doc, NamespaceDoc):
for field in STANDARD_FIELDS + user_docfields(api_doc, docindex):
add_metadata_from_var(api_doc, field)
# Extract a summary
if api_doc.summary is None and api_doc.descr is not None:
api_doc.summary, api_doc.other_docs = api_doc.descr.summary()
# If the summary is empty, but the return field is not, then use
# the return field to generate a summary description.
if (isinstance(api_doc, RoutineDoc) and api_doc.summary is None and
api_doc.return_descr is not None):
s, o = api_doc.return_descr.summary()
api_doc.summary = RETURN_PDS + s
api_doc.other_docs = o
# [XX] Make sure we don't have types/param descrs for unknown
# vars/params?
# Report any errors that occured
if api_doc in suppress_warnings:
if parse_errors or field_warnings:
log.info("Suppressing docstring warnings for %s, since it "
"is not included in the documented set." %
api_doc.canonical_name)
else:
report_errors(api_doc, docindex, parse_errors, field_warnings)
def add_metadata_from_var(api_doc, field):
for varname in field.varnames:
# Check if api_doc has a variable w/ the given name.
if varname not in api_doc.variables: continue
# Check moved here from before the for loop because we expect to
# reach rarely this point. The loop below is to be performed more than
# once only for fields with more than one varname, which currently is
# only 'author'.
for md in api_doc.metadata:
if field == md[0]:
return # We already have a value for this metadata.
var_doc = api_doc.variables[varname]
if var_doc.value is UNKNOWN: continue
val_doc = var_doc.value
value = []
# Try extracting the value from the pyval.
ok_types = (basestring, int, float, bool, type(None))
if val_doc.pyval is not UNKNOWN:
if isinstance(val_doc.pyval, ok_types):
value = [val_doc.pyval]
elif field.multivalue:
if isinstance(val_doc.pyval, (tuple, list)):
for elt in val_doc.pyval:
if not isinstance(elt, ok_types): break
else:
value = list(val_doc.pyval)
# Try extracting the value from the parse tree.
elif val_doc.toktree is not UNKNOWN:
try: value = [epydoc.docparser.parse_string(val_doc.toktree)]
except KeyboardInterrupt: raise
except: pass
if field.multivalue and not value:
try: value = epydoc.docparser.parse_string_list(val_doc.toktree)
except KeyboardInterrupt: raise
except: raise
# Add any values that we found.
for elt in value:
if isinstance(elt, str):
elt = decode_with_backslashreplace(elt)
else:
elt = unicode(elt)
elt = epytext.ParsedEpytextDocstring(
epytext.parse_as_para(elt), inline=True)
# Add in the metadata and remove from the variables
api_doc.metadata.append( (field, varname, elt) )
# Remove the variable itself (unless it's documented)
if var_doc.docstring in (None, UNKNOWN):
del api_doc.variables[varname]
if api_doc.sort_spec is not UNKNOWN:
try: api_doc.sort_spec.remove(varname)
except ValueError: pass
def initialize_api_doc(api_doc):
"""A helper function for L{parse_docstring()} that initializes
the attributes that C{parse_docstring()} will write to."""
if api_doc.descr is UNKNOWN:
api_doc.descr = None
if api_doc.summary is UNKNOWN:
api_doc.summary = None
if api_doc.metadata is UNKNOWN:
api_doc.metadata = []
if isinstance(api_doc, RoutineDoc):
if api_doc.arg_descrs is UNKNOWN:
api_doc.arg_descrs = []
if api_doc.arg_types is UNKNOWN:
api_doc.arg_types = {}
if api_doc.return_descr is UNKNOWN:
api_doc.return_descr = None
if api_doc.return_type is UNKNOWN:
api_doc.return_type = None
if api_doc.exception_descrs is UNKNOWN:
api_doc.exception_descrs = []
if isinstance(api_doc, (VariableDoc, PropertyDoc)):
if api_doc.type_descr is UNKNOWN:
api_doc.type_descr = None
if isinstance(api_doc, NamespaceDoc):
if api_doc.group_specs is UNKNOWN:
api_doc.group_specs = []
if api_doc.sort_spec is UNKNOWN:
api_doc.sort_spec = []
def split_init_fields(fields, warnings):
"""
Remove the fields related to the constructor from a class docstring
fields list.
@param fields: The fields to process. The list will be modified in place
@type fields: C{list} of L{markup.Field}
@param warnings: A list to emit processing warnings
@type warnings: C{list}
@return: The C{fields} items to be applied to the C{__init__} method
@rtype: C{list} of L{markup.Field}
"""
init_fields = []
# Split fields in lists according to their argument, keeping order.
arg_fields = {}
args_order = []
i = 0
while i < len(fields):
field = fields[i]
# gather together all the fields with the same arg
if field.arg() is not None:
arg_fields.setdefault(field.arg(), []).append(fields.pop(i))
args_order.append(field.arg())
else:
i += 1
# Now check that for each argument there is at most a single variable
# and a single parameter, and at most a single type for each of them.
for arg in args_order:
ff = arg_fields.pop(arg, None)
if ff is None:
continue
var = tvar = par = tpar = None
for field in ff:
if field.tag() in VARIABLE_TAGS:
if var is None:
var = field
fields.append(field)
else:
warnings.append(
"There is more than one variable named '%s'"
% arg)
elif field.tag() in PARAMETER_TAGS:
if par is None:
par = field
init_fields.append(field)
else:
warnings.append(
"There is more than one parameter named '%s'"
% arg)
elif field.tag() == 'type':
if var is None and par is None:
# type before obj
tvar = tpar = field
else:
if var is not None and tvar is None:
tvar = field
if par is not None and tpar is None:
tpar = field
elif field.tag() in EXCEPTION_TAGS:
init_fields.append(field)
else: # Unespected field
fields.append(field)
# Put selected types into the proper output lists
if tvar is not None:
if var is not None:
fields.append(tvar)
else:
pass # [xx] warn about type w/o object?
if tpar is not None:
if par is not None:
init_fields.append(tpar)
else:
pass # [xx] warn about type w/o object?
return init_fields
def report_errors(api_doc, docindex, parse_errors, field_warnings):
"""A helper function for L{parse_docstring()} that reports any
markup warnings and field warnings that we encountered while
processing C{api_doc}'s docstring."""
if not parse_errors and not field_warnings: return
# Get the name of the item containing the error, and the
# filename of its containing module.
name = api_doc.canonical_name
module = api_doc.defining_module
if module is not UNKNOWN and module.filename not in (None, UNKNOWN):
try: filename = py_src_filename(module.filename)
except: filename = module.filename
else:
filename = '??'
# [xx] Don't report markup errors for standard builtins.
# n.b. that we must use 'is' to compare pyvals here -- if we use
# 'in' or '==', then a user __cmp__ method might raise an
# exception, or lie.
if isinstance(api_doc, ValueDoc) and api_doc != module:
if module not in (None, UNKNOWN) and module.pyval is exceptions:
return
for builtin_val in __builtin__.__dict__.values():
if builtin_val is api_doc.pyval:
return
# Get the start line of the docstring containing the error.
startline = api_doc.docstring_lineno
if startline in (None, UNKNOWN):
startline = introspect_docstring_lineno(api_doc)
if startline in (None, UNKNOWN):
startline = None
# Display a block header.
header = 'File %s, ' % filename
if startline is not None:
header += 'line %d, ' % startline
header += 'in %s' % name
log.start_block(header)
# Display all parse errors. But first, combine any errors
# with duplicate description messages.
if startline is None:
# remove dups, but keep original order:
dups = {}
for error in parse_errors:
message = error.descr()
if message not in dups:
log.docstring_warning(message)
dups[message] = 1
else:
# Combine line number fields for dup messages:
messages = {} # maps message -> list of linenum
for error in parse_errors:
error.set_linenum_offset(startline)
message = error.descr()
messages.setdefault(message, []).append(error.linenum())
message_items = messages.items()
message_items.sort(lambda a,b:cmp(min(a[1]), min(b[1])))
for message, linenums in message_items:
linenums = [n for n in linenums if n is not None]
if len(linenums) == 0:
log.docstring_warning(message)
elif len(linenums) == 1:
log.docstring_warning("Line %s: %s" % (linenums[0], message))
else:
linenums = ', '.join(['%s' % l for l in linenums])
log.docstring_warning("Lines %s: %s" % (linenums, message))
# Display all field warnings.
for warning in field_warnings:
log.docstring_warning(warning)
# End the message block.
log.end_block()
RETURN_PDS = markup.parse('Returns:', markup='epytext')
"""A ParsedDocstring containing the text 'Returns'. This is used to
construct summary descriptions for routines that have empty C{descr},
but non-empty C{return_descr}."""
RETURN_PDS._tree.children[0].attribs['inline'] = True
######################################################################
#{ Field Processing Error Messages
######################################################################
UNEXPECTED_ARG = '%r did not expect an argument'
EXPECTED_ARG = '%r expected an argument'
EXPECTED_SINGLE_ARG = '%r expected a single argument'
BAD_CONTEXT = 'Invalid context for %r'
REDEFINED = 'Redefinition of %s'
UNKNOWN_TAG = 'Unknown field tag %r'
BAD_PARAM = '@%s for unknown parameter %s'
######################################################################
#{ Field Processing
######################################################################
def process_field(api_doc, docindex, tag, arg, descr):
"""
Process a single field, and use it to update C{api_doc}. If
C{tag} is the name of a special field, then call its handler
function. If C{tag} is the name of a simple field, then use
C{process_simple_field} to process it. Otherwise, check if it's a
user-defined field, defined in this docstring or the docstring of
a containing object; and if so, process it with
C{process_simple_field}.
@param tag: The field's tag, such as C{'author'}
@param arg: The field's optional argument
@param descr: The description following the field tag and
argument.
@raise ValueError: If a problem was encountered while processing
the field. The C{ValueError}'s string argument is an
explanation of the problem, which should be displayed as a
warning message.
"""
# standard special fields
if tag in _field_dispatch_table:
handler = _field_dispatch_table[tag]
handler(api_doc, docindex, tag, arg, descr)
return
# standard simple fields & user-defined fields
for field in STANDARD_FIELDS + user_docfields(api_doc, docindex):
if tag in field.tags:
# [xx] check if it's redefined if it's not multivalue??
if not field.takes_arg:
_check(api_doc, tag, arg, expect_arg=False)
api_doc.metadata.append((field, arg, descr))
return
# If we didn't handle the field, then report a warning.
raise ValueError(UNKNOWN_TAG % tag)
def user_docfields(api_doc, docindex):
"""
Return a list of user defined fields that can be used for the
given object. This list is taken from the given C{api_doc}, and
any of its containing C{NamepaceDoc}s.
@note: We assume here that a parent's docstring will always be
parsed before its childrens'. This is indeed the case when we
are called via L{docbuilder.build_doc_index()}. If a child's
docstring is parsed before its parents, then its parent won't
yet have had its C{extra_docstring_fields} attribute
initialized.
"""
docfields = []
# Get any docfields from `api_doc` itself
if api_doc.extra_docstring_fields not in (None, UNKNOWN):
docfields += api_doc.extra_docstring_fields
# Get any docfields from `api_doc`'s ancestors
for i in range(len(api_doc.canonical_name)-1, 0, -1):
ancestor = docindex.get_valdoc(api_doc.canonical_name[:i])
if ancestor is not None \
and ancestor.extra_docstring_fields not in (None, UNKNOWN):
docfields += ancestor.extra_docstring_fields
return docfields
_field_dispatch_table = {}
def register_field_handler(handler, *field_tags):
"""
Register the given field handler function for processing any
of the given field tags. Field handler functions should
have the following signature:
>>> def field_handler(api_doc, docindex, tag, arg, descr):
... '''update api_doc in response to the field.'''
Where C{api_doc} is the documentation object to update;
C{docindex} is a L{DocIndex} that can be used to look up the
documentation for related objects; C{tag} is the field tag that
was used; C{arg} is the optional argument; and C{descr} is the
description following the field tag and argument.
"""
for field_tag in field_tags:
_field_dispatch_table[field_tag] = handler
######################################################################
#{ Field Handler Functions
######################################################################
def process_summary_field(api_doc, docindex, tag, arg, descr):
"""Store C{descr} in C{api_doc.summary}"""
_check(api_doc, tag, arg, expect_arg=False)
if api_doc.summary is not None:
raise ValueError(REDEFINED % tag)
api_doc.summary = descr
def process_include_field(api_doc, docindex, tag, arg, descr):
"""Copy the docstring contents from the object named in C{descr}"""
_check(api_doc, tag, arg, expect_arg=False)
# options:
# a. just append the descr to our own
# b. append descr and update metadata
# c. append descr and process all fields.
# in any case, mark any errors we may find as coming from an
# imported docstring.
# how does this interact with documentation inheritance??
raise ValueError('%s not implemented yet' % tag)
def process_undocumented_field(api_doc, docindex, tag, arg, descr):
"""Remove any documentation for the variables named in C{descr}"""
_check(api_doc, tag, arg, context=NamespaceDoc, expect_arg=False)
for ident in _descr_to_identifiers(descr):
var_name_re = re.compile('^%s$' % ident.replace('*', '(.*)'))
for var_name, var_doc in api_doc.variables.items():
if var_name_re.match(var_name):
# Remove the variable from `variables`.
api_doc.variables.pop(var_name, None)
if api_doc.sort_spec is not UNKNOWN:
try: api_doc.sort_spec.remove(var_name)
except ValueError: pass
# For modules, remove any submodules that match var_name_re.
if isinstance(api_doc, ModuleDoc):
removed = set([m for m in api_doc.submodules
if var_name_re.match(m.canonical_name[-1])])
if removed:
# Remove the indicated submodules from this module.
api_doc.submodules = [m for m in api_doc.submodules
if m not in removed]
# Remove all ancestors of the indicated submodules
# from the docindex root. E.g., if module x
# declares y to be undocumented, then x.y.z should
# also be undocumented.
for elt in docindex.root[:]:
for m in removed:
if m.canonical_name.dominates(elt.canonical_name):
docindex.root.remove(elt)
def process_group_field(api_doc, docindex, tag, arg, descr):
"""Define a group named C{arg} containing the variables whose
names are listed in C{descr}."""
_check(api_doc, tag, arg, context=NamespaceDoc, expect_arg=True)
api_doc.group_specs.append( (arg, _descr_to_identifiers(descr)) )
# [xx] should this also set sort order?
def process_deffield_field(api_doc, docindex, tag, arg, descr):
"""Define a new custom field."""
_check(api_doc, tag, arg, expect_arg=True)
if api_doc.extra_docstring_fields is UNKNOWN:
api_doc.extra_docstring_fields = []
try:
docstring_field = _descr_to_docstring_field(arg, descr)
docstring_field.varnames.append("__%s__" % arg)
api_doc.extra_docstring_fields.append(docstring_field)
except ValueError, e:
raise ValueError('Bad %s: %s' % (tag, e))
def process_raise_field(api_doc, docindex, tag, arg, descr):
"""Record the fact that C{api_doc} can raise the exception named
C{tag} in C{api_doc.exception_descrs}."""
_check(api_doc, tag, arg, context=RoutineDoc, expect_arg='single')
try: name = DottedName(arg, strict=True)
except DottedName.InvalidDottedName: name = arg
api_doc.exception_descrs.append( (name, descr) )
def process_sort_field(api_doc, docindex, tag, arg, descr):
_check(api_doc, tag, arg, context=NamespaceDoc, expect_arg=False)
api_doc.sort_spec = _descr_to_identifiers(descr) + api_doc.sort_spec
# [xx] should I notice when they give a type for an unknown var?
def process_type_field(api_doc, docindex, tag, arg, descr):
# In namespace, "@type var: ..." describes the type of a var.
if isinstance(api_doc, NamespaceDoc):
_check(api_doc, tag, arg, expect_arg='single')
set_var_type(api_doc, arg, descr)
# For variables & properties, "@type: ..." describes the variable.
elif isinstance(api_doc, (VariableDoc, PropertyDoc)):
_check(api_doc, tag, arg, expect_arg=False)
if api_doc.type_descr is not None:
raise ValueError(REDEFINED % tag)
api_doc.type_descr = descr
# For routines, "@type param: ..." describes a parameter.
elif isinstance(api_doc, RoutineDoc):
_check(api_doc, tag, arg, expect_arg='single')
if arg in api_doc.arg_types:
raise ValueError(REDEFINED % ('type for '+arg))
api_doc.arg_types[arg] = descr
else:
raise ValueError(BAD_CONTEXT % tag)
def process_var_field(api_doc, docindex, tag, arg, descr):
_check(api_doc, tag, arg, context=ModuleDoc, expect_arg=True)
for ident in re.split('[:;, ] *', arg):
set_var_descr(api_doc, ident, descr)
def process_cvar_field(api_doc, docindex, tag, arg, descr):
# If @cvar is used *within* a variable, then use it as the
# variable's description, and treat the variable as a class var.
if (isinstance(api_doc, VariableDoc) and
isinstance(api_doc.container, ClassDoc)):
_check(api_doc, tag, arg, expect_arg=False)
api_doc.is_instvar = False
api_doc.descr = markup.ConcatenatedDocstring(api_doc.descr, descr)
api_doc.summary, api_doc.other_docs = descr.summary()
# Otherwise, @cvar should be used in a class.
else:
_check(api_doc, tag, arg, context=ClassDoc, expect_arg=True)
for ident in re.split('[:;, ] *', arg):
set_var_descr(api_doc, ident, descr)
api_doc.variables[ident].is_instvar = False
def process_ivar_field(api_doc, docindex, tag, arg, descr):
# If @ivar is used *within* a variable, then use it as the
# variable's description, and treat the variable as an instvar.
if (isinstance(api_doc, VariableDoc) and
isinstance(api_doc.container, ClassDoc)):
_check(api_doc, tag, arg, expect_arg=False)
# require that there be no other descr?
api_doc.is_instvar = True
api_doc.descr = markup.ConcatenatedDocstring(api_doc.descr, descr)
api_doc.summary, api_doc.other_docs = descr.summary()
# Otherwise, @ivar should be used in a class.
else:
_check(api_doc, tag, arg, context=ClassDoc, expect_arg=True)
for ident in re.split('[:;, ] *', arg):
set_var_descr(api_doc, ident, descr)
api_doc.variables[ident].is_instvar = True
# [xx] '@return: foo' used to get used as a descr if no other
# descr was present. is that still true?
def process_return_field(api_doc, docindex, tag, arg, descr):
_check(api_doc, tag, arg, context=RoutineDoc, expect_arg=False)
if api_doc.return_descr is not None:
raise ValueError(REDEFINED % 'return value description')
api_doc.return_descr = descr
def process_rtype_field(api_doc, docindex, tag, arg, descr):
_check(api_doc, tag, arg,
context=(RoutineDoc, PropertyDoc), expect_arg=False)
if isinstance(api_doc, RoutineDoc):
if api_doc.return_type is not None:
raise ValueError(REDEFINED % 'return value type')
api_doc.return_type = descr
elif isinstance(api_doc, PropertyDoc):
_check(api_doc, tag, arg, expect_arg=False)
if api_doc.type_descr is not None:
raise ValueError(REDEFINED % tag)
api_doc.type_descr = descr
def process_arg_field(api_doc, docindex, tag, arg, descr):
_check(api_doc, tag, arg, context=RoutineDoc, expect_arg=True)
idents = re.split('[:;, ] *', arg)
api_doc.arg_descrs.append( (idents, descr) )
# Check to make sure that the documented parameter(s) are
# actually part of the function signature.
all_args = api_doc.all_args()
if all_args not in (['...'], UNKNOWN):
bad_params = ['"%s"' % i for i in idents if i not in all_args]
if bad_params:
raise ValueError(BAD_PARAM % (tag, ', '.join(bad_params)))
def process_kwarg_field(api_doc, docindex, tag, arg, descr):
# [xx] these should -not- be checked if they exist..
# and listed separately or not??
_check(api_doc, tag, arg, context=RoutineDoc, expect_arg=True)
idents = re.split('[:;, ] *', arg)
api_doc.arg_descrs.append( (idents, descr) )
register_field_handler(process_group_field, 'group')
register_field_handler(process_deffield_field, 'deffield', 'newfield')
register_field_handler(process_sort_field, 'sort')
register_field_handler(process_summary_field, 'summary')
register_field_handler(process_undocumented_field, 'undocumented')
register_field_handler(process_include_field, 'include')
register_field_handler(process_var_field, 'var', 'variable')
register_field_handler(process_type_field, 'type')
register_field_handler(process_cvar_field, 'cvar', 'cvariable')
register_field_handler(process_ivar_field, 'ivar', 'ivariable')
register_field_handler(process_return_field, 'return', 'returns')
register_field_handler(process_rtype_field, 'rtype', 'returntype')
register_field_handler(process_arg_field, 'arg', 'argument',
'parameter', 'param')
register_field_handler(process_kwarg_field, 'kwarg', 'keyword', 'kwparam')
register_field_handler(process_raise_field, 'raise', 'raises',
'except', 'exception')
# Tags related to function parameters
PARAMETER_TAGS = ('arg', 'argument', 'parameter', 'param',
'kwarg', 'keyword', 'kwparam')
# Tags related to variables in a class
VARIABLE_TAGS = ('cvar', 'cvariable', 'ivar', 'ivariable')
# Tags related to exceptions
EXCEPTION_TAGS = ('raise', 'raises', 'except', 'exception')
######################################################################
#{ Helper Functions
######################################################################
def check_type_fields(api_doc, field_warnings):
"""Check to make sure that all type fields correspond to some
documented parameter; if not, append a warning to field_warnings."""
if isinstance(api_doc, RoutineDoc):
for arg in api_doc.arg_types:
if arg not in api_doc.all_args():
for args, descr in api_doc.arg_descrs:
if arg in args:
break
else:
field_warnings.append(BAD_PARAM % ('type', '"%s"' % arg))
def set_var_descr(api_doc, ident, descr):
if ident not in api_doc.variables:
api_doc.variables[ident] = VariableDoc(
container=api_doc, name=ident,
canonical_name=api_doc.canonical_name+ident)
var_doc = api_doc.variables[ident]
if var_doc.descr not in (None, UNKNOWN):
raise ValueError(REDEFINED % ('description for '+ident))
var_doc.descr = descr
if var_doc.summary in (None, UNKNOWN):
var_doc.summary, var_doc.other_docs = var_doc.descr.summary()
def set_var_type(api_doc, ident, descr):
if ident not in api_doc.variables:
api_doc.variables[ident] = VariableDoc(
container=api_doc, name=ident,
canonical_name=api_doc.canonical_name+ident)
var_doc = api_doc.variables[ident]
if var_doc.type_descr not in (None, UNKNOWN):
raise ValueError(REDEFINED % ('type for '+ident))
var_doc.type_descr = descr
def _check(api_doc, tag, arg, context=None, expect_arg=None):
if context is not None:
if not isinstance(api_doc, context):
raise ValueError(BAD_CONTEXT % tag)
if expect_arg is not None:
if expect_arg == True:
if arg is None:
raise ValueError(EXPECTED_ARG % tag)
elif expect_arg == False:
if arg is not None:
raise ValueError(UNEXPECTED_ARG % tag)
elif expect_arg == 'single':
if (arg is None or ' ' in arg):
raise ValueError(EXPECTED_SINGLE_ARG % tag)
else:
assert 0, 'bad value for expect_arg'
def get_docformat(api_doc, docindex):
"""
Return the name of the markup language that should be used to
parse the API documentation for the given object.
"""
# Find the module that defines api_doc.
module = api_doc.defining_module
# Look up its docformat.
if module is not UNKNOWN and module.docformat not in (None, UNKNOWN):
docformat = module.docformat
else:
docformat = DEFAULT_DOCFORMAT
# Convert to lower case & strip region codes.
try: return docformat.lower().split()[0]
except: return DEFAULT_DOCFORMAT
def unindent_docstring(docstring):
# [xx] copied from inspect.getdoc(); we can't use inspect.getdoc()
# itself, since it expects an object, not a string.
if not docstring: return ''
lines = docstring.expandtabs().split('\n')
# Find minimum indentation of any non-blank lines after first line.
margin = sys.maxint
for line in lines[1:]:
content = len(line.lstrip())
if content:
indent = len(line) - content
margin = min(margin, indent)
# Remove indentation.
if lines:
lines[0] = lines[0].lstrip()
if margin < sys.maxint:
for i in range(1, len(lines)): lines[i] = lines[i][margin:]
# Remove any trailing (but not leading!) blank lines.
while lines and not lines[-1]:
lines.pop()
#while lines and not lines[0]:
# lines.pop(0)
return '\n'.join(lines)
_IDENTIFIER_LIST_REGEXP = re.compile(r'^[\w.\*]+([\s,:;]\s*[\w.\*]+)*$')
def _descr_to_identifiers(descr):
"""
Given a C{ParsedDocstring} that contains a list of identifiers,
return a list of those identifiers. This is used by fields such
as C{@group} and C{@sort}, which expect lists of identifiers as
their values. To extract the identifiers, the docstring is first
converted to plaintext, and then split. The plaintext content of
the docstring must be a a list of identifiers, separated by
spaces, commas, colons, or semicolons.
@rtype: C{list} of C{string}
@return: A list of the identifier names contained in C{descr}.
@type descr: L{markup.ParsedDocstring}
@param descr: A C{ParsedDocstring} containing a list of
identifiers.
@raise ValueError: If C{descr} does not contain a valid list of
identifiers.
"""
idents = descr.to_plaintext(None).strip()
idents = re.sub(r'\s+', ' ', idents)
if not _IDENTIFIER_LIST_REGEXP.match(idents):
raise ValueError, 'Bad Identifier list: %r' % idents
rval = re.split('[:;, ] *', idents)
return rval
def _descr_to_docstring_field(arg, descr):
tags = [s.lower() for s in re.split('[:;, ] *', arg)]
descr = descr.to_plaintext(None).strip()
args = re.split('[:;,] *', descr)
if len(args) == 0 or len(args) > 3:
raise ValueError, 'Wrong number of arguments'
singular = args[0]
if len(args) >= 2: plural = args[1]
else: plural = None
short = 0
if len(args) >= 3:
if args[2] == 'short': short = 1
else: raise ValueError('Bad arg 2 (expected "short")')
return DocstringField(tags, singular, plural, short)
######################################################################
#{ Function Signature Extraction
######################################################################
# [XX] todo: add optional type modifiers?
_SIGNATURE_RE = re.compile(
# Class name (for builtin methods)
r'^\s*((?P<self>\w+)\.)?' +
# The function name (must match exactly) [XX] not anymore!
r'(?P<func>\w+)' +
# The parameters
r'\((?P<params>(\s*\[?\s*\*{0,2}[\w\-\.]+(\s*=.+?)?'+
r'(\s*\[?\s*,\s*\]?\s*\*{0,2}[\w\-\.]+(\s*=.+?)?)*\]*)?)\s*\)' +
# The return value (optional)
r'(\s*(->)\s*(?P<return>\S.*?))?'+
# The end marker
r'\s*(\n|\s+(--|<=+>)\s+|$|\.\s+|\.\n)')
"""A regular expression that is used to extract signatures from
docstrings."""
def parse_function_signature(func_doc, doc_source, docformat, parse_errors):
"""
Construct the signature for a builtin function or method from
its docstring. If the docstring uses the standard convention
of including a signature in the first line of the docstring
(and formats that signature according to standard
conventions), then it will be used to extract a signature.
Otherwise, the signature will be set to a single varargs
variable named C{"..."}.
@param func_doc: The target object where to store parsed signature. Also
container of the docstring to parse if doc_source is C{None}
@type func_doc: L{RoutineDoc}
@param doc_source: Contains the docstring to parse. If C{None}, parse
L{func_doc} docstring instead
@type doc_source: L{APIDoc}
@rtype: C{None}
"""
if doc_source is None:
doc_source = func_doc
# If there's no docstring, then don't do anything.
if not doc_source.docstring: return False
m = _SIGNATURE_RE.match(doc_source.docstring)
if m is None: return False
# Do I want to be this strict?
# Notice that __init__ must match the class name instead, if the signature
# comes from the class docstring
# if not (m.group('func') == func_doc.canonical_name[-1] or
# '_'+m.group('func') == func_doc.canonical_name[-1]):
# log.warning("Not extracting function signature from %s's "
# "docstring, since the name doesn't match." %
# func_doc.canonical_name)
# return False
params = m.group('params')
rtype = m.group('return')
selfparam = m.group('self')
# Extract the parameters from the signature.
func_doc.posargs = []
func_doc.vararg = None
func_doc.kwarg = None
if func_doc.posarg_defaults is UNKNOWN:
func_doc.posarg_defaults = []
if params:
# Figure out which parameters are optional.
while '[' in params or ']' in params:
m2 = re.match(r'(.*)\[([^\[\]]+)\](.*)', params)
if not m2: return False
(start, mid, end) = m2.groups()
mid = re.sub(r'((,|^)\s*[\w\-\.]+)', r'\1=...', mid)
params = start+mid+end
params = re.sub(r'=...=' , r'=', params)
for name in params.split(','):
if '=' in name:
(name, default_repr) = name.split('=',1)
default = GenericValueDoc(parse_repr=default_repr)
else:
default = None
name = name.strip()
if name == '...':
func_doc.vararg = '...'
elif name.startswith('**'):
func_doc.kwarg = name[2:]
elif name.startswith('*'):
func_doc.vararg = name[1:]
else:
func_doc.posargs.append(name)
if len(func_doc.posarg_defaults) < len(func_doc.posargs):
func_doc.posarg_defaults.append(default)
elif default is not None:
argnum = len(func_doc.posargs)-1
func_doc.posarg_defaults[argnum] = default
# Extract the return type/value from the signature
if rtype:
func_doc.return_type = markup.parse(rtype, docformat, parse_errors,
inline=True)
# Add the self parameter, if it was specified.
if selfparam:
func_doc.posargs.insert(0, selfparam)
func_doc.posarg_defaults.insert(0, None)
# Remove the signature from the docstring.
doc_source.docstring = doc_source.docstring[m.end():]
# We found a signature.
return True
|