| #!/usr/bin/env python |
| # posix.py - Convert The Open Group troff sources to posix man pages |
| # |
| # Copyright (C) 2013 Felix Janda |
| # |
| # Permission is hereby granted, free of charge, to any person |
| # obtaining a copy of this software and associated documentation files |
| # (the "Software"), to deal in the Software without restriction, |
| # including without limitation the rights to use, copy, modify, merge, |
| # publish, distribute, sublicense, and/or sell copies of the Software, |
| # and to permit persons to whom the Software is furnished to do so, |
| # subject to the following conditions: |
| # |
| # The above copyright notice and this permission notice shall be |
| # included in all copies or substantial portions of the Software. |
| # |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| # SOFTWARE. |
| |
| import sys |
| import os.path |
| import re |
| |
| # Helper for troff() |
| def quote(arg): |
| if " " in arg or "\\" in arg: return '"{}"'.format(arg) |
| else: return arg |
| |
| # troff(".BL", "some text", ",") --> '.B "some text" ,\n' |
| def troff(macro, *args): |
| if len(args) == 1 and type(args[0]) is list: |
| args = args[0] |
| args = [quote(arg) for arg in args] |
| s = ".{} {}".format(macro, " ".join(args)) |
| return s.strip(" ") + "\n" |
| |
| # For first processing |
| # Replace a macro by a string |
| brepl = {"mH":".SH", |
| "Cm":".I", |
| "Ev":".I", |
| "Ar":".I", |
| "yS":".LP\n.nf", |
| "yE":".fi", |
| "Cs":".sp\n.RS 4\n.nf\n\\fB", |
| "Ce":".fi \\fR\n.P\n.RE", |
| "HU":".SS", |
| "cV":".B", |
| "Fi":".B", |
| "rS":'.LP\n.I "The following sections are informative."', |
| "Cd":".I"} |
| # Surround the argument of a macro by two strings. |
| sur = {"LM":["{", "}"], |
| "Hd":[".I <", "> "], |
| "tK":["<", ">"], |
| "cH":[".B '", "' "], |
| "sG":[".B \\(dq", "\\(dq "], |
| "Fn":["\\fI","\\fR()"], |
| "Er":[".B [", "] "]} |
| # Delete a macro |
| delete = {"xR", |
| "iX", |
| "sS", |
| "sE", |
| "sP", |
| "TH", |
| "SK", |
| "sT", |
| "mS", |
| "mE", |
| "rE", |
| "po", |
| "if", |
| "ne", |
| "nr", |
| "tr", |
| "in"} |
| # Places where we might need to insert .RS to get indentation right |
| indentpoint = {"P", "BL", "DL", "VL", "AL", "Cs", "Ns"} |
| # Places where we might need to insert .RE to get indentation right |
| indentend = {"LE", "LI", "Ne"} |
| # Generic replacements in the seconds processing |
| repl = {"\\f1":"\\fR", |
| "\\f2":"\\fI", |
| "\\f3":"\\fB", |
| "\\f5":"\\fR", |
| "\\f6":"\\fI", |
| "\\f7":"\\fB", |
| "XBD\n":"the \\*(Zz,\n", |
| "XSH\n":"the \\*(Zy,\n", |
| "XCU\n":"the \\*(Zx,\n", |
| "XRAT\n":"the \\*(zj,\n", |
| "\\*(x":"", |
| "\\*(z!":"", |
| "\\*(z?":"", |
| ".TS H":".TS", |
| "\\(*D":" ", |
| ".B ":".BR ", |
| ".I ":".IR ", |
| '" "$':"$"} |
| |
| # Here it starts |
| |
| if len(sys.argv) == 1: |
| progname = os.path.basename(sys.argv[0]) |
| sys.stderr.write("Usage: {} SECTION [FILE...]\n".format(progname)) |
| exit(2) |
| section = sys.argv[1] |
| |
| # Add the escape sequences from _strings to the repl dictionary |
| lines=open("_strings").readlines() |
| for l in lines: |
| p = l.partition("\t") |
| repl[p[0]] = p[2].strip("\n") |
| |
| # Generate refdictorig from the ,xref file. It will be later extended to |
| # the refdict dictionary with table and figure names from the current man |
| # page |
| refdictregs = [[r'\\fB<([a-zA-Z0-9\._/]*)>\\fP$', r'.BR \1 ({}p)'], |
| [r'\\fI([a-zA-Z0-9_]*)\\fR\\\^\(\\\|\)$', r'.BR \1 ({}p)'], |
| [r'\\fI([a-zA-Z0-9]*)\\fR\\\^$', r'.BR \1 ({}p)']] |
| refdictorig = dict() |
| lines = open(",xref").readlines() |
| nsec = -1 # current section |
| for l in lines: |
| if l.startswith("intro_"): nsec += 1 |
| p = l.partition(" ") |
| s = p[2].strip("\n").replace(', ', '" ", " "') |
| for reg in refdictregs: # Format references to other man pages |
| s = re.sub(reg[0], reg[1].format([0, 3, 1][nsec]), s) |
| if s[0] != ".": # Otherwise make it italic |
| s = '.I "{}"'.format(s) |
| if ".h" in s: s = s.replace("/", "_") # for <sys/types.h> |
| refdictorig[p[0]] = s |
| |
| for file in sys.argv[2:]: |
| input = open(file) |
| lines = input.readlines() |
| input.close() |
| |
| # Complete the ref descriptions |
| refdict = refdictorig |
| for i, l in enumerate(lines): |
| l = l.strip("\n") |
| if l.startswith(".xR ") and l[4] in ["6", "7"]: |
| key = l.split(" ")[2] |
| # Add numbers to tables and figures in current page |
| lines[i - 1] += ' " ' + refdict[key].split(" ")[2] |
| refdict[key] = '{}, {}"'.format(refdict[key].strip('"'), |
| prev.split(' "')[1].strip('"')) |
| prev = l |
| |
| in_DSI = 0 # Are we in a display? |
| list_stack = [["BL"]] # stack of groff_mm lists we are in |
| list_num_stack = [] # stack of current list item numbers |
| needindent = 0 # do we need to insert .RS before the next paragraph? |
| |
| # First processing |
| for i, l in enumerate(lines): |
| # only consider lines with macros |
| if not l or l[0] != ".": continue |
| |
| # read out macro name with its arguments |
| macro = l.strip("\n").partition(" ")[0][1:] |
| args = ["".join(t) for t in re.findall(r'([^\s"][^\s]*"*)|"([^"]*)"', |
| l.strip("\n"))][1:] |
| |
| # strip comments |
| if macro.startswith('\\"'): lines[i] = "" |
| |
| insert = "" # string to be inserted in front of current line |
| if needindent > 0: |
| if macro in indentpoint: |
| insert = ".RS {} \n".format(needindent) |
| needindent = -1 |
| if needindent == -1 and macro in indentend: |
| insert = ".RE\n" |
| |
| # "Note:" macros |
| if macro == "Ns": |
| lines[i] = ".TP 10\n.B Note" |
| if args: lines[i] += "s" |
| lines[i] += ":\n" |
| needindent = 10 |
| if macro == "Ne": |
| lines[i] = troff("P") |
| needindent = -1 |
| |
| # Read the name of the current man page from the mS macro |
| if macro == "mS": name = args[0] |
| |
| # mm display macros |
| if macro == "DS": |
| if "I" in args: |
| lines[i] = ".sp\n.RS\n" |
| in_DSI = 1 |
| else: lines[i] = "" |
| elif macro == "DE": |
| if in_DSI: lines[i] = troff("RE") |
| else: lines[i] = "" |
| in_DSI = 0 |
| |
| # Fix table and figure captions |
| if macro == "TB": |
| if len(args) == 1: args.append("") |
| lines[i] = ".sp\n.ce 1\n\\fBTable{}: {}\\fR\n".format(args[1], args[0]) |
| elif macro == "FG": |
| if len(args) == 1: args.append("") |
| lines[i] = ".sp\n.ce 1\n\\fBFigure{}: {}\\fR\n".format(args[1], args[0]) |
| # strip unecessary macros around figures |
| elif macro == "F+": |
| lines[i] = lines[i + 1] = lines[i + 2] = "" |
| |
| # mm list macro processing |
| elif macro in {"BL", "DL", "VL", "AL"}: # start of a list |
| needindent = 0 |
| list_stack.insert(0, [macro, args]) |
| if list_stack[0][0] == "AL": |
| list_num_stack.insert(0, 1) |
| lines[i] = "" |
| elif macro == "LI": # list item |
| # Strip some unecessary escape sequences to make the regexes for |
| # formatting the ERRORS section happy |
| if args: args[0] = args[0].replace("\\*!", "") |
| |
| needindent = 4 |
| if list_stack[0][0] == "BL": # bullet list |
| li_args = [" *", "4"] |
| elif list_stack[0][0] == "DL": # dashed list |
| li_args = ["--", "4"] |
| elif list_stack[0][0] == "VL": # variable item list |
| needindent = (int(list_stack[0][1][0]) - 8) // 2 + 8 |
| li_args = [args[0], str(needindent)] |
| elif list_stack[0][0] == "AL": # advanced list |
| num = list_num_stack[0] |
| s = "" |
| if not list_stack[0][1]: # 1. 2. 3. |
| s = str(num) |
| elif list_stack[0][1][0] == "a": # a. b. c. |
| s = chr(num + 96) |
| elif list_stack[0][1][0] == "i": # i. ii. iii. |
| needindent += 1 |
| s = "".join(["i" for j in range(num)]) |
| li_args = ["{:>2s}.".format(s), str(needindent)] |
| list_num_stack[0] += 1 |
| lines[i] = troff("IP", li_args) |
| elif macro == "LE": # list end |
| if list_stack[0][0] == "AL": |
| del list_num_stack[0] |
| del list_stack[0] |
| lines[i] = "" |
| needindent = -1 |
| if len(list_stack) >= 2: |
| lines[i] = troff("LE") + troff("RE") |
| |
| # References |
| elif macro == "cX": |
| key = args[0] |
| if key in refdict: s = refdict[key] |
| else: s = key |
| if len(args) > 1: s += args[1] |
| lines[i] = s + "\n" |
| |
| # Generic replacements |
| if macro in delete: |
| lines[i] = "" |
| elif macro in brepl: |
| lines[i] = l.replace("." + macro, brepl[macro], 1) |
| elif macro in sur: |
| lines[i] = sur[macro][0] + args[0] + sur[macro][1] + " ".join(args[1:]) + "\n" |
| |
| lines[i] = insert + lines[i] |
| |
| # Second processing |
| in_SEE_ALSO = 0 # Are we in the SEE ALSO section? |
| in_EQ = 0 # Are we in a displayed equation? |
| for i, l in enumerate(lines): |
| if l == ".SH \"SEE ALSO\"\n": |
| in_SEE_ALSO = 1 |
| elif l == ".SH \"CHANGE HISTORY\"\n": |
| lines = lines[:i] # We are not allowed to include the CHANGE HISTORY |
| break |
| elif l == ".EQ\n": |
| in_EQ = 1 |
| elif l == ".EN\n": |
| in_EQ = 0 |
| # hack the equations to be a bit better readable on terminals |
| if in_EQ or '$' in l: |
| l = re.sub(r' sup ([a-zA-Z0-9\\\(]*)', "\"^\" \\1\" \"", l) |
| l = re.sub(r' sub ([a-zA-Z0-9\\\(]*)', "_ \\1\" \"", l) |
| l = l.replace("left { ~\n", "") |
| l = l.replace("~", ' " " ') |
| # reformat the error codes in the ERRORS section |
| l = re.sub(r'.IP \[(E[0-9A-Z]*)\] [0-9]*\n', ".TP\n.B \\1\n", l) |
| l = re.sub(r'.IP "\[(E[0-9A-Z]*)\] or \[(E[0-9A-Z]*)\]" [0-9]*\n', |
| ".TP\n.BR \\1 \" or \" \\2\n", l) |
| # Strip some extra space in the beginning of lines |
| l = re.sub(r'^\\ ', "", l) |
| # Generic replacements |
| for me in repl: |
| l = l.replace(me, repl[me]) |
| for me in repl: # To be sure... |
| l = l.replace(me, repl[me]) |
| if in_SEE_ALSO: l = re.sub(r'^the', "The", l) |
| lines[i] = l |
| |
| name = re.sub(r'^<|>$', r'', name).replace("/", "_") |
| # name printed on the top of the man page |
| if section != "0p": NAME = name.upper() |
| else: NAME = name |
| lines.insert(0, |
| "'\\\" et\n" |
| ".TH {} \"{}\" 2013 \"IEEE/The Open Group\" \"POSIX Programmer's Manual\"\n" |
| ".SH PROLOG\n" |
| "This manual page is part of the POSIX Programmer's Manual.\n" |
| "The Linux implementation of this interface may differ (consult\n" |
| "the corresponding Linux manual page for details of Linux behavior),\n" |
| "or the interface may not be implemented on Linux.\n" |
| "\n" |
| "".format(NAME, section.upper())) |
| lines.append( |
| ".SH COPYRIGHT\n" |
| "Portions of this text are reprinted and reproduced in electronic form\n" |
| "from IEEE Std 1003.1, 2013 Edition, Standard for Information Technology\n" |
| "-- Portable Operating System Interface (POSIX), The Open Group Base\n" |
| "Specifications Issue 7, Copyright (C) 2013 by the Institute of\n" |
| "Electrical and Electronics Engineers, Inc and The Open Group.\n" |
| "(This is POSIX.1-2008 with the 2013 Technical Corrigendum 1 applied.) In the\n" |
| "event of any discrepancy between this version and the original IEEE and\n" |
| "The Open Group Standard, the original IEEE and The Open Group Standard\n" |
| "is the referee document. The original Standard can be obtained online at\n" |
| "http://www.unix.org/online.html .\n" |
| "\n" |
| "Any typographical or formatting errors that appear\n" |
| "in this page are most likely\n" |
| "to have been introduced during the conversion of the source files to\n" |
| "man page format. To report such errors, see\n" |
| "https://www.kernel.org/doc/man-pages/reporting_bugs.html .\n" |
| ) |
| |
| text = "".join(lines) |
| # Final hacks for the indentation |
| for i in [[".LE\n.RE\n.P", ".P"], [".LE\n.RE\n.RE", ".RE"], |
| [".RE\n.LE\n.RE", ".RE"], ["\n.LE\n", "\n"]]: |
| text = text.replace(i[0], i[1]) |
| output = open(name + "." + section, 'w') |
| output.write(text) |
| output.close() |