diff -r 6fab89460407 .hgignore --- a/.hgignore Sat Aug 16 18:45:39 2014 -0400 +++ b/.hgignore Sun Aug 17 09:39:06 2014 -0400 @@ -51,6 +51,7 @@ *~ Lib/lib2to3/*.pickle Lib/test/data/* +Lib/test/os_exception_hierarchy.txt Misc/*.wpu PC/python_nt*.h PC/pythonnt_rc*.h diff -r 6fab89460407 Doc/conf.py --- a/Doc/conf.py Sat Aug 16 18:45:39 2014 -0400 +++ b/Doc/conf.py Sun Aug 17 09:39:06 2014 -0400 @@ -7,6 +7,7 @@ # that aren't pickleable (module imports are okay, they're removed automatically). import sys, os, time +from warnings import warn sys.path.append(os.path.abspath('tools/sphinxext')) # General configuration @@ -196,3 +197,20 @@ coverage_ignore_c_items = { # 'cfunction': [...] } + +# Build OS exceptions hierarchy mapping to errnos to be used +# in Doc/library/exceptions.rst. +sys.path.append(os.path.abspath('tools')) +try: + import build_table +except: + build_table = None + warn('Could not import build table in order to build OS_exceptions' + ' hierarchy.', + Warning) + +if build_table is not None: + build_table.main('../Lib/test/exception_hierarchy.txt', + 'library/exceptions.rst', + '../Lib/test/os_exception_hierarchy.txt') +sys.path.pop() diff -r 6fab89460407 Doc/library/exceptions.rst --- a/Doc/library/exceptions.rst Sat Aug 16 18:45:39 2014 -0400 +++ b/Doc/library/exceptions.rst Sun Aug 17 09:39:06 2014 -0400 @@ -573,6 +573,10 @@ :pep:`3151` - Reworking the OS and IO exception hierarchy +The following table is a mapping of OS exceptions to corresponding errno values. + +.. literalinclude:: ../../Lib/test/os_exception_hierarchy.txt + Warnings -------- diff -r 6fab89460407 Doc/tools/build_table.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Doc/tools/build_table.py Sun Aug 17 09:39:06 2014 -0400 @@ -0,0 +1,162 @@ +import os +from warnings import warn + +def get_file(path): + with open(path, 'r') as f: + return f.read() + +def write_file(path, s): + with open(path, 'w') as f: + f.write(s) + +def get_section(string, start, end, funcs): + """Given a string, finds the line beginning with specified + start string using given string function and condition. + All lines are appended until the end condition is met using + the given end function. + + Returns: a list of lines in the specified range. + """ + start_func, start_cond, end_func, end_cond = funcs + lines = [] + i = 0 + line_in = False + for row in string.split('\n'): + if start_func(row.lstrip(), start) != start_cond: + line_in = True + if line_in: + if end_func(row.lstrip(), end) != end_cond and i != 0: + return lines + lines.append(row) + i = 1 + +def get_error_name(string): + """Extracts error name from a line in the ascii table.""" + return string[string.rfind(' ') + 1:] + +def get_ascii(string): + """Extracts the ascii structure from a line in the ascii table, + but removes the + and returns a | in its place. + """ + return string[:string.rfind('+')] + '|' + +def get_errnos(error, doc): + """Given a list of lines, finds the specified exception, + and then scrapes the errnos values listed directly afterwards, + returning everything after :c:data:`errno`. The errnos are + conveniently located at the end of a paragraph, so an empty + line delimits the end of the errnos. This is an assumption, + and if the structure of the documentation changes, this will + break. + + Returns: a string of raw errnos (Punctuation and spaces included). + """ + done = False + errnos = [] + for row in range(len(doc)): + if doc[row].startswith('.. exception::') and doc[row].find(error) != -1: + break + while True: + if row == len(doc) - 1: + return None + search = doc[row].find(':c:data:`errno`') + if search != -1: + break + row += 1 + s = doc[row][search + len(':c:data:`errno`'):] + while True: + row += 1 + if doc[row].isspace() or doc[row] == '': + return s + s += doc[row] + +def extract_errnos(s): + """Returns a list of all words in a raw errno.""" + start = end = -1 + errnos = [] + for c in range(len(s)): + if s[c].isalpha(): + if start == -1: + start = c + end = c + elif (not s[c].isalpha()) and ((start != -1) and (end != -1)): + errnos.append(s[start:end + 1]) + start = end = -1 + return errnos + +def format_row(left, right, width): + """Returns a string that right justifies the string + given by the parameter right according to the width parameter, + with the left parameter left justified. + """ + right = right.rjust(width - len(left)) + return '{}{}'.format(left, right) + +def make_table(hier, errnos, table_width=80): + """Given an ascii exception hierarchy and a dict mapping + an exception to its associated errnos, prints a table that + depicts the relation. The width of the table is controlled + through the table_width parameter + + Returns: a string table. + """ + lines = [] + lines.append(format_row('OS exception', 'errno', table_width)) + lines.extend([format_row('_'*len('OS exception'), '_'*len('errno'), table_width), '']) + + for row in hier: + exc_name = get_error_name(row) + row = row.lstrip() + if exc_name in errnos: + errno = errnos[exc_name] + else: + errno = '' + lines.extend([format_row(row, errno, table_width), get_ascii(row)]) + return '\n'.join(lines[:-1]) + +def main(exc_hierarchy_path, exceptions_documentation_path, out_path): + """Controlling function. Receives a path to the exception hierarchy + table, a path to the exceptions documentation, and an output path. + + This function finds and extracts the OS exceptions section of the + table, as well as the OS exceptions section of the documentation. + For each exception in the hierarchy, the associated errno values + are found in the documentation. Then a table is constructed + mapping between the OS exception hierarchy on the left and + related errnos on the right. + + Writes a table to outpath, which will be added to the documentation + in the OS exceptions section of exceptions.rst through a + literalinclude. + """ + try: + loc = locals() + for name, path in loc.items(): + if 'path' in name: + loc[name] = os.path.abspath(path) + + exc_table = get_file(loc['exc_hierarchy_path']) + os_table = get_section(exc_table, 'OSError', '+', + (str.find, -1, str.startswith, False)) + + names = [get_error_name(row) for row in os_table] + + exc_doc = get_file(loc['exceptions_documentation_path']) + os_doc = get_section(exc_doc, 'OS exceptions', 'Warnings', + (str.startswith, False, str.startswith, False)) + + errnos = {} + for name in names[1:]: + s = get_errnos(name, os_doc) + s = extract_errnos(s) + if 'and' in s: s.remove('and') + s = ', '.join(s) + errnos[name] = s + + table = make_table(os_table, errnos, 70) + write_file(loc['out_path'], table) + except: + warn('OS exceptions hierarchy table being build from:\n{exc_hierarchy_path}\n' + 'and:\n{exceptions_documentation_path}\ndid not correctly write to:\n{out_path}\n' + 'See Doc/tools/build_table.py and Doc/conf.py for details'.format(**loc), + Warning)