Deroffer work

This commit is contained in:
ridiculousfish 2012-04-08 01:43:30 -07:00
parent e2c3ca9950
commit 576c12b184
3 changed files with 867 additions and 12 deletions

View File

@ -1,6 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*-
# Run me like this: ./create_manpage_completions.py /usr/share/man/man1/* > man_completions.fish """ # Run me like this: ./create_manpage_completions.py /usr/share/man/man1/* > man_completions.fish
""" """
<OWNER> = Siteshwar Vashisht <OWNER> = Siteshwar Vashisht
@ -17,6 +18,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
""" """
import string, sys, re, os.path, gzip, traceback import string, sys, re, os.path, gzip, traceback
from deroff import Deroffer
# This gets set to the name of the command that we are currently executing # This gets set to the name of the command that we are currently executing
CMDNAME = "" CMDNAME = ""
@ -478,7 +480,7 @@ class Type4ManParser(ManParser):
def name(self): def name(self):
return "Type4" return "Type4"
class TypeMacManParser(ManParser): class TypeDarwinManParser(ManParser):
def isMyType(self, manpage): def isMyType(self, manpage):
options_section_matched = compileAndSearch("\.S[hH] DESCRIPTION", manpage) options_section_matched = compileAndSearch("\.S[hH] DESCRIPTION", manpage)
return options_section_matched != None return options_section_matched != None
@ -539,6 +541,53 @@ class TypeMacManParser(ManParser):
return "Darwin man parser" return "Darwin man parser"
class TypeDeroffManParser(ManParser):
def isMyType(self, manpage):
return True # We're optimists
def is_option(self, line):
return line.startswith('-')
def could_be_description(self, line):
return len(line) > 0 and not line.startswith('-')
def parseManPage(self, manpage):
d = Deroffer()
d.deroff(manpage)
output = d.get_output()
lines = output.split('\n')
got_something = False
# Discard lines until we get to DESCRIPTION or OPTIONS
while lines and not (lines[0].startswith('DESCRIPTION') or lines[0].startswith('OPTIONS') or lines[0].startswith('COMMAND OPTIONS')):
lines.pop(0)
while lines:
# Pop until we get to the next option
while lines and not self.is_option(lines[0]):
lines.pop(0)
if not lines:
continue
options = lines.pop(0)
# Pop until we get to either an empty line or a line starting with -
description = ''
while lines and self.could_be_description(lines[0]):
if description: description += ' '
description += lines.pop(0)
builtcommand(options, description)
got_something = True
return got_something
def name(self):
return "Deroffing man parser"
def parse_manpage_at_path(manpage_path): def parse_manpage_at_path(manpage_path):
filename = os.path.basename(manpage_path) filename = os.path.basename(manpage_path)
@ -568,10 +617,21 @@ def parse_manpage_at_path(manpage_path):
if cmd_base in ignoredcommands: if cmd_base in ignoredcommands:
return return
# Ignore perl's gazillion man pages
ignored_prefixes = ['perl', 'zsh']
for prefix in ignored_prefixes:
if cmd_base.startswith(prefix):
return
# Ignore the millions of links to BUILTIN(1)
if manpage.find('BUILTIN 1') != -1:
return
# Clear the output list # Clear the output list
built_command_output[:] = [] built_command_output[:] = []
parsers = [Type1ManParser(), Type2ManParser(), Type4ManParser(), Type3ManParser(), TypeMacManParser()] parsers = [Type1ManParser(), Type2ManParser(), Type4ManParser(), Type3ManParser(), TypeDarwinManParser(), TypeDeroffManParser()]
parsersToTry = [p for p in parsers if p.isMyType(manpage)] parsersToTry = [p for p in parsers if p.isMyType(manpage)]
success = False success = False
@ -593,7 +653,8 @@ def parse_manpage_at_path(manpage_path):
add_diagnostic(manpage_path + ' parsed successfully') add_diagnostic(manpage_path + ' parsed successfully')
else: else:
parser_names = ', '.join(p.name() for p in parsersToTry) parser_names = ', '.join(p.name() for p in parsersToTry)
add_diagnostic('%s is unparsable (tried parser %s)' % (manpage_path, parser_names), True) #add_diagnostic('%s contains no options or is unparsable' % manpage_path, True)
add_diagnostic('%s contains no options or is unparsable (tried parser %s)' % (manpage_path, parser_names), True)
return success return success
def compare_paths(a, b): def compare_paths(a, b):
@ -612,6 +673,8 @@ def parse_and_output_man_pages(paths):
except IOError: except IOError:
diagnostic_indent = 0 diagnostic_indent = 0
add_diagnostic('Cannot open ' + manpage_path) add_diagnostic('Cannot open ' + manpage_path)
except (KeyboardInterrupt, SystemExit):
raise
except: except:
add_diagnostic('Error parsing %s: %s' % (manpage_path, sys.exc_info()[0]), True) add_diagnostic('Error parsing %s: %s' % (manpage_path, sys.exc_info()[0]), True)
flush_diagnostics(sys.stderr) flush_diagnostics(sys.stderr)
@ -627,11 +690,11 @@ if __name__ == "__main__":
VERBOSE = True VERBOSE = True
args.pop(0) args.pop(0)
if False:
parse_and_output_man_pages(args) parse_and_output_man_pages(args)
else:
# Profiling code # Profiling code
# import cProfile, pstats import cProfile, pstats
# cProfile.run('parse_and_output_man_pages(paths)', 'fooprof') cProfile.run('parse_and_output_man_pages(args)', 'fooprof')
# p = pstats.Stats('fooprof') p = pstats.Stats('fooprof')
# p.sort_stats('cumulative').print_stats(10) p.sort_stats('cumulative').print_stats(100)

791
share/tools/deroff.py Executable file
View File

@ -0,0 +1,791 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
class Deroffer:
def __init__(self):
self.reg_table = {}
self.tr = range(256)
self.nls = 2
self.specletter = False
self.pretty = False
self.refer = False
self.macro = 0
self.nobody = False
self.inlist = False
self.inheader = False
self.pic = False
self.tbl = False
self.tblstate = 0
self.tblTab = ''
self.eqn = False
self.skipheaders = False
self.skiplists = False
self.ignore_sonx = False
self.output = []
self.OPTIONS = 0
self.FORMAT = 1
self.DATA = 2
# words is uninteresting and should be treated as false
def flush_output(self, where):
if where:
where.write(''.join(self.output))
self.output[:] = []
def get_output(self):
return ''.join(self.output)
def putchar(self, c):
self.output.append(c)
return c
def condputchar(self, c):
ci = ord(c)
ci_trans = self.tr[ci & 0xFF]
c_trans = chr(ci_trans)
if not self.pic and not self.eqn and not self.refer and not self.macro and (not self.skiplists or not self.inlist) and (not self.skipheaders or not self.inheader):
if self.pretty:
if c == '\n':
self.nls += 1
if self.nls > 2: return c
else:
self.nls = 0
return self.putchar(c_trans)
else:
return self.putchar(c_trans)
elif not self.pretty and c == '\n':
return self.putchar(c_trans)
else:
return c
def condputs(self, str):
for c in str:
self.condputchar(c)
def str_at(self, idx):
return self.s[idx:idx+1]
def skip_char(self, amt=1):
self.s = self.s[amt:]
def skip_leading_whitespace(self):
self.s = self.s.lstrip()
def is_white(self, idx):
# Note this returns false for empty strings (idx >= len(self.s))
return self.str_at(idx).isspace()
def prefix_at(self, offset, other_str):
return self.s[offset:].startswith(other_str)
def str_eq(offset, other, len):
return self.s[offset:offset+len] == other[:len]
def prch(self, idx):
ch = self.str_at(idx)
return ch and ch not in ' \t\n'
def font(self):
if self.str_at(0) == '\\' and self.str_at(1) == 'f':
if self.str_at(2) == '(' and self.prch(3) and self.prch(4):
self.skip_char(5)
return True
elif self.str_at(2) == '[':
self.skip_char(2)
while self.prch(0) and self.str_at(0) != ']': self.skip_char()
if self.str_at(0) == ']': self.skip_char()
elif self.prch(2):
self.skip_char(3)
return True
return False
def comment(self):
if self.str_at(0) == '\\' and self.str_at(1) == '"':
while self.str_at(0) and self.str_at(0) != '\n': self.skip_char()
return True
return False
def numreq(self):
if self.str_at(0) == '\\' and self.str_at(1) in 'hvwud' and self.str_at(2) == '\'':
self.macro += 1
self.skip_char(3)
while self.str_at(0) != '\'' and self.esc_char():
pass # Weird
if self.str_at(0) == '\'':
self.skip_char()
self.macro -= 1
return True
return False
def var(self):
reg = ''
if self.s.startswith('\\n'):
if self.s[3:5].startswith('dy'):
self.skip_char(5)
return True
elif self.str_at(2) == '(' and self.prch(3) and self.prch(4):
self.skip_char(5)
return True
elif self.str_at(2) == '[' and self.prch(3):
self.skip_char(3)
while self.str_at(0) and self.str_at(0) != ']':
self.skip_char()
return True
elif self.prch(2):
self.skip_char(3)
return True
elif self.s.startswith('\\*'):
if self.str_at(2) == '(' and self.prch(3) and self.prch(4):
reg = self.s[3:5]
self.skip_char(5)
elif self.str_at(2) == '[' and self.prch(3):
self.skip_char(3)
while self.str_at(0) and self.str_at(0) != ']':
reg = reg + self.str_at(0)
self.skip_char()
if self.s.startswith(']'):
self.skip_char()
else:
return False
elif self.prch(2):
reg = self.str_at(2)
self.skip_char(3)
else:
return False
if reg in self.reg_table:
old_s = self.s
self.s = self.reg_table[reg]
self.text_arg()
return True
return False
def size(self):
if self.str_at(0) == '\\' and self.str_at(1) == 's' and (self.digit(2) or (self.str_at(2) in '-+' and self.digit(3))):
self.skip_char(3)
while self.digit(0): self.skip_char()
return True
return False
def spec(self):
self.specletter = False
if self.s.startswith('\\(') and self.prch(2) and self.prch(3):
specs_specletter = {
# Output composed latin1 letters
'-D': '\320',
'Sd': '\360',
'Tp': '\376',
'TP': '\336',
'AE': '\306',
'ae': '\346',
'OE': "OE",
'oe': "oe",
':a': '\344',
':A': '\304',
':e': '\353',
':E': '\313',
':i': '\357',
':I': '\317',
':o': '\366',
':O': '\326',
':u': '\374',
':U': '\334',
':y': '\377',
'ss': '\337',
'\'A': '\301',
'\'E': '\311',
'\'I': '\315',
'\'O': '\323',
'\'U': '\332',
'\'Y': '\335',
'\'a': '\341',
'\'e': '\351',
'\'i': '\355',
'\'o': '\363',
'\'u': '\372',
'\'y': '\375',
'^A': '\302',
'^E': '\312',
'^I': '\316',
'^O': '\324',
'^U': '\333',
'^a': '\342',
'^e': '\352',
'^i': '\356',
'^o': '\364',
'^u': '\373',
'`A': '\300',
'`E': '\310',
'`I': '\314',
'`O': '\322',
'`U': '\331',
'`a': '\340',
'`e': '\350',
'`i': '\354',
'`o': '\362',
'`u': '\371',
'~A': '\303',
'~N': '\321',
'~O': '\325',
'~a': '\343',
'~n': '\361',
'~o': '\365',
',C': '\307',
',c': '\347',
'/l': "/l",
'/L': "/L",
'/o': '\370',
'/O': '\330',
'oA': '\305',
'oa': '\345',
# Ligatures
'fi': 'fi',
'ff': 'ff',
'fl': 'fl',
'Fi': 'ffi',
'Ff': 'fff',
'Fl': 'ffl'
}
specs = {
'mi': '-',
'en': '-',
'hy': '-',
'em': "--",
'lq': "``",
'rq': "\'\'",
'Bq': ",,",
'oq': '`',
'cq': '\'',
'aq': '\'',
'dq': '"',
'or': '|',
'at': '@',
'sh': '#',
'Eu': '\244',
'eu': '\244',
'Do': '$',
'ct': '\242',
'Fo': '\253',
'Fc': '\273',
'fo': '<',
'fc': '>',
'r!': '\241',
'r?': '\277',
'Of': '\252',
'Om': '\272',
'pc': '\267',
'S1': '\271',
'S2': '\262',
'S3': '\263',
'<-': "<-",
'->': "->",
'<>': "<->",
'ua': '^',
'da': 'v',
'lA': "<=",
'rA': "=>",
'hA': "<=>",
'uA': "^^",
'dA': "vv",
'ba': '|',
'bb': '|',
'br': '|',
'bv': '|',
'ru': '_',
'ul': '_',
'ci': 'O',
'bu': 'o',
'co': '\251',
'rg': '\256',
'tm': "(TM)",
'dd': "||",
'dg': '|',
'ps': '\266',
'sc': '\247',
'de': '\260',
'%0': "0/00",
'14': '\274',
'12': '\275',
'34': '\276',
'f/': '/',
'sl': '/',
'rs': '\\',
'sq': "[]",
'fm': '\'',
'ha': '^',
'ti': '~',
'lB': '[',
'rB': ']',
'lC': '{',
'rC': '}',
'la': '<',
'ra': '>',
'lh': "<=",
'rh': "=>",
'tf': "therefore",
'~~': "~~",
'~=': "~=",
'!=': "!=",
'**': '*',
'+-': '\261',
'<=': "<=",
'==': "==",
'=~': "=~",
'>=': ">=",
'AN': "\\/",
'OR': "/\\",
'no': '\254',
'te': "there exists",
'fa': "for all",
'Ah': "aleph",
'Im': "imaginary",
'Re': "real",
'if': "infinity",
'md': "\267",
'mo': "member of",
'mu': '\327',
'nm': "not member of",
'pl': '+',
'eq': '=',
'pt': "oc",
'pp': "perpendicular",
'sb': "(=",
'sp': "=)",
'ib': "(-",
'ip': "-)",
'ap': '~',
'is': 'I',
'sr': "root",
'pd': 'd',
'c*': "(x)",
'c+': "(+)",
'ca': "cap",
'cu': 'U',
'di': '\367',
'gr': 'V',
'es': "{}",
'CR': "_|",
'st': "such that",
'/_': "/_",
'lz': "<>",
'an': '-',
# Output Greek
'*A': "Alpha",
'*B': "Beta",
'*C': "Xi",
'*D': "Delta",
'*E': "Epsilon",
'*F': "Phi",
'*G': "Gamma",
'*H': "Theta",
'*I': "Iota",
'*K': "Kappa",
'*L': "Lambda",
'*M': "Mu",
'*N': "Nu",
'*O': "Omicron",
'*P': "Pi",
'*Q': "Psi",
'*R': "Rho",
'*S': "Sigma",
'*T': "Tau",
'*U': "Upsilon",
'*W': "Omega",
'*X': "Chi",
'*Y': "Eta",
'*Z': "Zeta",
'*a': "alpha",
'*b': "beta",
'*c': "xi",
'*d': "delta",
'*e': "epsilon",
'*f': "phi",
'+f': "phi",
'*g': "gamma",
'*h': "theta",
'+h': "theta",
'*i': "iota",
'*k': "kappa",
'*l': "lambda",
'*m': "\265",
'*n': "nu",
'*o': "omicron",
'*p': "pi",
'+p': "omega",
'*q': "psi",
'*r': "rho",
'*s': "sigma",
'*t': "tau",
'*u': "upsilon",
'*w': "omega",
'*x': "chi",
'*y': "eta",
'*z': "zeta",
'ts': "sigma",
}
key = self.s[2:4]
if key in specs_specletter:
self.condputs(specs_specletter[key])
self.specletter = True
elif key in specs:
self.condputs(specs[key])
self.skip_char(4)
return True
elif self.s.startswith('\\%'):
self.specletter = True
self.skip_char(2)
return True
else:
return False
def esc(self):
if self.str_at(0) == '\\' and self.str_at(1):
c = self.str_at(1)
if c in 'eE':
self.condputchar('\\')
elif c in 't':
self.condputchar('\t')
elif c in '0~':
self.condputchar(' ')
elif c in '|^&:':
pass
else:
self.condputchar(c)
self.skip_char(2)
return True
return False
def word(self):
if self.letter(0):
self.condputchar(self.str_at(0))
self.skip_char()
while True:
if self.spec() and not self.specletter:
break
else:
if self.letter(0):
self.condputchar(self.str_at(0))
self.skip_char()
else:
break
return True
return False
def text(self):
while True:
if not self.esc_char():
if self.str_at(0):
self.condputchar(self.str_at(0))
self.skip_char()
else:
break
return True
def letter(self, idx):
ch = self.str_at(idx)
return ch.isalpha() or ch == '_' # underscore is used in C identifiers
def digit(self, idx):
ch = self.str_at(idx)
return ch.isdigit()
def number(self):
if (self.str_at(0) in '+-' and self.digit(1)) or self.digit(0):
self.condputchar(self.str_at(0))
self.skip_char()
while self.digit(0):
self.condputchar(self.str_at(0))
self.skip_char()
return True
return False
def esc_char(self):
if self.s.startswith('\\'):
if self.comment() or self.font() or self.size() or self.numreq() or self.var() or self.spec() or self.esc():
return True
return self.word() or self.number()
def quoted_arg(self):
if self.str_at(0) == '"':
self.skip_char()
while self.s and self.str_at(0) != '"':
if not self.esc_char():
if self.s:
self.condputchar(self.str_at(0))
self.skip_char()
return True
else:
return False
def text_arg(self):
if not self.esc_char():
if self.s and not self.is_white(0):
self.condputchar(self.str_at(0))
self.skip_char()
else:
return False
while True:
if not self.esc_char():
if self.s and not self.is_white(0):
self.condputchar(self.str_at(0))
self.skip_char()
else:
return True
def request_or_macro(self):
self.skip_char()
s0 = self.str_at(0)
if s0 == '\\':
if self.str_at(1) == '"':
if not self.pretty: self.condputchar('\n')
return True
else:
pass
elif s0 == '[':
self.refer = True
if not self.pretty: self.condputchar('\n')
return True
elif s0 == ']':
self.refer = False
self.skip_char()
return self.text()
elif s0 == '.':
self.macro = False
if not self.pretty: self.condputchar('\n')
return True
self.nobody = False
s0s1 = self.s[0:2]
if s0s1 == 'SH':
for header_str in [' SYNOPSIS', ' "SYNOPSIS', ' BERSICHT', ' "BERSICHT']:
if self.s[2:].startswith(header_str):
if self.pretty: self.condputchar('\n')
self.inheader = True
break
else:
# Did not find a header string
self.inheader = False
if self.pretty: self.condputchar('\n')
self.nobody = True
elif s0s1 in ['SS', 'IP', 'H ']:
if self.pretty: self.condputchar('\n')
self.nobody = True
elif s0s1 in ['I ', 'IR', 'IB', 'B ', 'BR', 'BI', 'R ', 'RB', 'RI', 'AB']:
pass
elif s0s1 in ['] ']:
self.refer = False
elif s0s1 in ['PS']:
if self.is_white(2): self.pic = True
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['PE']:
if self.is_white(2): self.pic = False
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['TS']:
if self.is_white(2): self.tbl, self.tblstate = True, self.OPTIONS
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['T&']:
if self.is_white(2): self.tbl, self.tblstate = True, self.FORMAT
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['TE']:
if self.is_white(2): self.tbl = False
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['EQ']:
if self.is_white(2): self.eqn = True
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['EN']:
if self.is_white(2): self.eqn = False
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['R1']:
if self.is_white(2): self.refer2 = True
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['R2']:
if self.is_white(2): self.refer2 = False
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['de']:
macro=True
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['BL', 'VL', 'AL', 'LB', 'RL', 'ML', 'DL']:
if self.is_white(2): self.inlist = True
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['BV']:
if self.str_at(2) == 'L' and self.white(self.str_at(3)): self.inlist = True
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['LE']:
if self.is_white(2): self.inlist = False
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['LP', 'PP', 'P\n']:
if self.pretty: self.condputchar('\n')
self.condputchar('\n')
return True
elif s0s1 in ['ds']:
self.skip_char(2)
self.skip_leading_whitespace()
if self.str_at(0):
# Split at whitespace
comps = self.s.split(None, 2)
if len(comps) is 2:
name, value = comps
value = value.rstrip()
self.reg_table[name] = value
if not self.pretty: self.condputchar('\n')
return True
elif s0s1 in ['so', 'nx']:
# We always ignore include directives
# deroff.c for some reason allowed this to fall through to the 'tr' case
# I think that was just a bug so I won't replicate it
return True
elif s0s1 in ['tr']:
self.skip_char(2)
self.skip_leading_whitespace()
while self.str_at(0) and self.str_at(0) != '\n':
c = ord(self.str_at(0))
self.skip_char()
ns = self.str_at(0)
self.skip_char()
if not ns or ns == '\n':
self.tr[c] = ord(' ')
else:
self.tr[c] = ord(ns)
return True
elif s0s1 in ['sp']:
if self.pretty: self.condputchar('\n')
self.condputchar('\n')
return True
else:
if not self.pretty: self.condputchar('\n')
return True
if self.skipheaders and self.nobody: return True
self.skip_leading_whitespace()
while self.s and not self.is_white(0): self.skip_char()
self.skip_leading_whitespace()
while True:
if not self.quoted_arg() and not self.text_arg():
if self.s:
self.condputchar(self.str_at(0))
self.skip_char()
else:
return True
def do_tbl(self):
if self.tblstate == self.OPTIONS:
while self.s and self.str_at(0) != ';' and self.str_at(0) != '\n':
self.skip_leading_whitespace()
if not self.str_at(0).isalpha():
# deroff.c has a bug where it can loop forever here...we try to work around it
self.skip_char()
else: # Parse option
option = self.s
arg = ''
idx = 0
while option[idx:idx+1].isalpha():
idx += 1
if option[idx:idx+1] == '(':
option = option[:idx]
self.s = self.s[idx+1:]
arg = self.s
else:
self.s = ''
if arg:
idx = arg.find(')')
if idx != -1:
arg = arg[:idx]
self.s = self.s[idx+1:]
else:
#self.skip_char()
pass
if option.lower() == 'tab':
self.tblTab = arg[0:1]
self.tblstate = self.FORMAT
if not self.pretty: self.condputchar('\n')
elif self.tblstate == self.FORMAT:
while self.s and self.str_at(0) != '.' and self.str_at(0) != '\n':
self.skip_leading_whitespace()
if self.str_at(0): self.skip_char()
if self.str_at(0) == '.': self.tblstate = self.DATA
if not self.pretty: self.condputchar('\n')
elif self.tblstate == self.DATA:
if self.tblTab:
self.s = self.s.replace(self.tblTab, '\t')
self.text()
return True
def do_line(self):
if not self.s: return True
if self.str_at(0) == '.' or self.str_at(0) == '\'':
if not self.request_or_macro(): return False
elif self.tbl:
self.do_tbl()
else:
self.text()
return True
def deroff(self, str):
lines = str.split('\n')
for line in lines:
line = line + '\n'
self.s = line
if not self.do_line():
break
def deroff_files(files):
for arg in files:
print >> sys.stderr, arg
if arg.endswith('.gz'):
f = gzip.open(arg, 'r')
else:
f = open(arg, 'r')
str = f.read()
d = Deroffer()
d.deroff(str)
d.flush_output(sys.stdout)
f.close()
if __name__ == "__main__":
import gzip
paths = sys.argv[1:]
if True:
deroff_files(paths)
else:
import cProfile, pstats
cProfile.run('deroff_files(paths)', 'fooprof')
p = pstats.Stats('fooprof')
p.sort_stats('cumulative').print_stats(100)

View File

@ -15,6 +15,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
""" """
import os, sys, re import os, sys, re
import "deroff"
config_file = None config_file = None
prompt_buff = "" prompt_buff = ""