"""Try to detect suspicious constructs, resembling markupthat has leaked into the final output.Suspicious lines are reported in a comma-separated-file,``suspicious.csv``, located in the output directory.The file is utf-8 encoded, and each line contains four fields:* document name (normalized)* line number in the source document* problematic text* complete line showing the problematic text in contextIt is common to find many false positives. To avoid reporting themagain and again, they may be added to the ``ignored.csv`` file(located in the configuration directory). The file has the sameformat as ``suspicious.csv`` with a few differences:- each line defines a rule; if the rule matches, the issueis ignored.- line number may be empty (that is, nothing between thecommas: ",,"). In this case, line numbers are ignored (therule matches anywhere in the file).- the last field does not have to be a complete line; somesurrounding text (never more than a line) is enough forcontext.Rules are processed sequentially. A rule matches when:* document names are the same* problematic texts are the same* line numbers are close to each other (5 lines up or down)* the rule text is completely contained into the source lineThe simplest way to create the ignored.csv file is by copyingundesired entries from suspicious.csv (possibly trimming the lastfield.)Copyright 2009 Gabriel A. Genellina"""import osimport reimport csvimport sysfrom docutils import nodesfrom sphinx.builders import Builderimport sphinx.utildetect_all = re.compile(r'''::(?=[^=])| # two :: (but NOT ::=):[a-zA-Z][a-zA-Z0-9]+| # :foo`| # ` (seldom used by itself)(?<!\.)\.\.[ \t]*\w+: # .. foo: (but NOT ... else:)''', re.UNICODE | re.VERBOSE).finditerpy3 = sys.version_info >= (3, 0)class Rule:def __init__(self, docname, lineno, issue, line):"""A rule for ignoring issues"""self.docname = docname # document to which this rule appliesself.lineno = lineno # line number in the original source;# this rule matches only near that.# None -> don't careself.issue = issue # the markup fragment that triggered this ruleself.line = line # text of the container element (single line only)self.used = Falsedef __repr__(self):return '{0.docname},,{0.issue},{0.line}'.format(self)class dialect(csv.excel):"""Our dialect: uses only linefeed as newline."""lineterminator = '\n'class CheckSuspiciousMarkupBuilder(Builder):"""Checks for possibly invalid markup that may leak into the output."""name = 'suspicious'logger = sphinx.util.logging.getLogger("CheckSuspiciousMarkupBuilder")def init(self):# create output fileself.log_file_name = os.path.join(self.outdir, 'suspicious.csv')open(self.log_file_name, 'w').close()# load database of previously ignored issuesself.load_rules(os.path.join(os.path.dirname(__file__), '..','susp-ignored.csv'))def get_outdated_docs(self):return self.env.found_docsdef get_target_uri(self, docname, typ=None):return ''def prepare_writing(self, docnames):passdef write_doc(self, docname, doctree):# set when any issue is encountered in this documentself.any_issue = Falseself.docname = docnamevisitor = SuspiciousVisitor(doctree, self)doctree.walk(visitor)def finish(self):unused_rules = [rule for rule in self.rules if not rule.used]if unused_rules:self.warn('Found %s/%s unused rules:' %(len(unused_rules), len(self.rules)))for rule in unused_rules:self.logger.info(repr(rule))returndef check_issue(self, line, lineno, issue):if not self.is_ignored(line, lineno, issue):self.report_issue(line, lineno, issue)def is_ignored(self, line, lineno, issue):"""Determine whether this issue should be ignored."""docname = self.docnamefor rule in self.rules:if rule.docname != docname: continueif rule.issue != issue: continue# Both lines must match *exactly*. This is rather strict,# and probably should be improved.# Doing fuzzy matches with levenshtein distance could work,# but that means bringing other libraries...# Ok, relax that requirement: just check if the rule fragment# is contained in the document lineif rule.line not in line: continue# Check both line numbers. If they're "near"# this rule matches. (lineno=None means "don't care")if (rule.lineno is not None) and \abs(rule.lineno - lineno) > 5: continue# if it came this far, the rule matchedrule.used = Truereturn Truereturn Falsedef report_issue(self, text, lineno, issue):if not self.any_issue: self.logger.info()self.any_issue = Trueself.write_log_entry(lineno, issue, text)if py3:self.warn('[%s:%d] "%s" found in "%-.120s"' %(self.docname, lineno, issue, text))else:self.warn('[%s:%d] "%s" found in "%-.120s"' % (self.docname.encode(sys.getdefaultencoding(),'replace'),lineno,issue.encode(sys.getdefaultencoding(),'replace'),text.strip().encode(sys.getdefaultencoding(),'replace')))self.app.statuscode = 1def write_log_entry(self, lineno, issue, text):if py3:f = open(self.log_file_name, 'a')writer = csv.writer(f, dialect)writer.writerow([self.docname, lineno, issue, text.strip()])f.close()else:f = open(self.log_file_name, 'ab')writer = csv.writer(f, dialect)writer.writerow([self.docname.encode('utf-8'),lineno,issue.encode('utf-8'),text.strip().encode('utf-8')])f.close()def load_rules(self, filename):"""Load database of previously ignored issues.A csv file, with exactly the same format as suspicious.csvFields: document name (normalized), line number, issue, surrounding text"""self.logger.info("loading ignore rules... ", nonl=1)self.rules = rules = []try:if py3:f = open(filename, 'r')else:f = open(filename, 'rb')except IOError:returnfor i, row in enumerate(csv.reader(f)):if len(row) != 4:raise ValueError("wrong format in %s, line %d: %s" % (filename, i+1, row))docname, lineno, issue, text = rowif lineno:lineno = int(lineno)else:lineno = Noneif not py3:docname = docname.decode('utf-8')issue = issue.decode('utf-8')text = text.decode('utf-8')rule = Rule(docname, lineno, issue, text)rules.append(rule)f.close()self.logger.info('done, %d rules loaded' % len(self.rules))def get_lineno(node):"""Obtain line number information for a node."""lineno = Nonewhile lineno is None and node:node = node.parentlineno = node.linereturn linenodef extract_line(text, index):"""text may be a multiline string; extractonly the line containing the given character index.>>> extract_line("abc\ndefgh\ni", 6)>>> 'defgh'>>> for i in (0, 2, 3, 4, 10):... print extract_line("abc\ndefgh\ni", i)abcabcabcdefghdefghi"""p = text.rfind('\n', 0, index) + 1q = text.find('\n', index)if q < 0:q = len(text)return text[p:q]class SuspiciousVisitor(nodes.GenericNodeVisitor):lastlineno = 0def __init__(self, document, builder):nodes.GenericNodeVisitor.__init__(self, document)self.builder = builderdef default_visit(self, node):if isinstance(node, (nodes.Text, nodes.image)): # direct text containerstext = node.astext()# lineno seems to go backwards sometimes (?)self.lastlineno = lineno = max(get_lineno(node) or 0, self.lastlineno)seen = set() # don't report the same issue more than only once per linefor match in detect_all(text):issue = match.group()line = extract_line(text, match.start())if (issue, line) not in seen:self.builder.check_issue(line, lineno, issue)seen.add((issue, line))unknown_visit = default_visitdef visit_document(self, node):self.lastlineno = 0def visit_comment(self, node):# ignore comments -- too much false positives.# (although doing this could miss some errors;# there were two sections "commented-out" by mistake# in the Python docs that would not be caught)raise nodes.SkipNode
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。