rst_parser.py 27.8 KB
Newer Older
1
import os, sys, re, string, glob
2
allmodules = ["core", "flann", "imgproc", "ml", "highgui", "video", "features2d", "calib3d", "objdetect", "legacy", "contrib", "gpu", "androidcamera", "java", "python", "stitching", "ts", "photo", "nonfree", "videostab"]
3 4 5
verbose = False
show_warnings = True
show_errors = True
6
show_critical_errors = True
7

8 9 10 11 12 13 14 15 16 17
params_blacklist = {
    "fromarray" : ("object", "allowND"), # python only function
    "reprojectImageTo3D" : ("ddepth"),   # python only argument
    "composeRT" : ("d*d*"),              # wildchards in parameter names are not supported by this parser
    "CvSVM::train_auto" : ("\*Grid"),    # wildchards in parameter names are not supported by this parser
    "error" : "args", # parameter of supporting macro
    "getConvertElem" : ("from", "cn", "to", "beta", "alpha"), # arguments of returned functions
    "gpu::swapChannels" : ("dstOrder") # parameter is not parsed correctly by the hdr_parser
}

18
ERROR_001_SECTIONFAILURE      = 1
19
WARNING_002_HDRWHITESPACE     = 2
20 21 22 23 24 25 26 27
ERROR_003_PARENTHESES         = 3
WARNING_004_TABS              = 4
ERROR_005_REDEFENITIONPARAM   = 5
ERROR_006_REDEFENITIONFUNC    = 6
WARNING_007_UNDOCUMENTEDPARAM = 7
WARNING_008_MISSINGPARAM      = 8
WARNING_009_HDRMISMATCH       = 9
ERROR_010_NOMODULE            = 10
28
ERROR_011_EOLEXPECTED         = 11
29

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
params_mapping = {
    "composeRT" : {
        "dr3dr1" : "d*d*",
        "dr3dr2" : "d*d*",
        "dr3dt1" : "d*d*",
        "dr3dt2" : "d*d*",
        "dt3dr1" : "d*d*",
        "dt3dr2" : "d*d*",
        "dt3dt1" : "d*d*",
        "dt3dt2" : "d*d*"
        },
    "CvSVM::train_auto" : {
        "coeffGrid" : "\\*Grid",
        "degreeGrid" : "\\*Grid",
        "gammaGrid" : "\\*Grid",
        "nuGrid" : "\\*Grid",
        "pGrid" : "\\*Grid"
    }
}

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
class DeclarationParser(object):
    def __init__(self, line=None):
        if line is None:
            self.fdecl = ""
            self.lang = ""
            self.balance = 0
            return
        self.lang = self.getLang(line)
        assert self.lang is not None
        self.fdecl = line[line.find("::")+2:].strip()
        self.balance = self.fdecl.count("(") - self.fdecl.count(")")
        assert self.balance >= 0

    def append(self, line):
        self.fdecl += line
        self.balance = self.fdecl.count("(") - self.fdecl.count(")")

    def isready(self):
        return self.balance == 0

    def getLang(self, line):
        if line.startswith(".. ocv:function::"):
            return "C++"
        if line.startswith(".. ocv:cfunction::"):
            return "C"
        if line.startswith(".. ocv:pyfunction::"):
            return "Python2"
        if line.startswith(".. ocv:pyoldfunction::"):
            return "Python1"
        if line.startswith(".. ocv:jfunction::"):
            return "Java"
        return None
82

83 84 85 86 87 88 89 90 91 92 93 94 95 96
    def hasDeclaration(self, line):
        return self.getLang(line) is not None

class ParamParser(object):
    def __init__(self, line=None):
        if line is None:
            self.prefix = ""
            self.name = ""
            self.comment = ""
            self.active = False
            return
        offset = line.find(":param")
        assert offset > 0
        self.prefix = line[:offset]
97
        assert self.prefix==" "*len(self.prefix), ":param definition should be prefixed with spaces"
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
        line = line[offset + 6:].lstrip()
        name_end = line.find(":")
        assert name_end > 0
        self.name = line[:name_end]
        self.comment = line[name_end+1:].lstrip()
        self.active = True

    def append(self, line):
        assert self.active
        if (self.hasDeclaration(line)):
            self.active = False
        elif line.startswith(self.prefix) or not line:
            self.comment += "\n" + line.lstrip()
        else:
            self.active = False
113

114 115 116 117 118 119 120
    def hasDeclaration(self, line):
        return line.lstrip().startswith(":param")

class RstParser(object):
    def __init__(self, cpp_parser):
        self.cpp_parser = cpp_parser
        self.definitions = {}
121 122 123
        self.sections_parsed = 0
        self.sections_total = 0
        self.sections_skipped = 0
124

125 126 127
    def parse(self, module_name, module_path=None):
        if module_path is None:
            module_path = "../" + module_name
128 129
        doclist = glob.glob(os.path.join(module_path,"doc/*.rst"))
        for doc in doclist:
130
            self.parse_rst_file(module_name, doc)
131

132 133 134 135
    def parse_section_safe(self, module_name, section_name, file_name, lineno, lines):
        try:
            self.parse_section(module_name, section_name, file_name, lineno, lines)
        except AssertionError, args:
136
            if show_errors:
137
                print >> sys.stderr, "RST parser error E%03d: assertion in \"%s\" at %s:%s" % (ERROR_001_SECTIONFAILURE, section_name, file_name, lineno)
138
                print >> sys.stderr, "    Details: %s" % args
139 140 141 142

    def parse_section(self, module_name, section_name, file_name, lineno, lines):
        self.sections_total += 1
        # skip sections having whitespace in name
143 144
        #if section_name.find(" ") >= 0 and section_name.find("::operator") < 0:
        if section_name.find(" ") >= 0 and not bool(re.match(r"(\w+::)*operator\s*(\w+|>>|<<|\(\)|->|\+\+|--|=|==|\+=|-=)", section_name)):
145
            if show_errors:
A
Andrey Kamaev 已提交
146
                print >> sys.stderr, "RST parser warning W%03d:  SKIPPED: \"%s\" File: %s:%s" % (WARNING_002_HDRWHITESPACE, section_name, file_name, lineno)
147 148
            self.sections_skipped += 1
            return
149 150 151 152 153

        func = {}
        func["name"] = section_name
        func["file"] = file_name
        func["line"] = lineno
154
        func["module"] = module_name
155 156

        # parse section name
157 158
        section_name = self.parse_namespace(func, section_name)
        class_separator_idx = section_name.find("::")
159
        if class_separator_idx > 0:
160 161
            func["class"] = section_name[:class_separator_idx]
            func["method"] = section_name[class_separator_idx+2:]
162
        else:
163
            func["method"] = section_name
164

165
        capturing_seealso = False
166 167 168 169 170 171 172 173 174 175 176 177 178
        skip_code_lines = False
        expected_brief = True
        fdecl = DeclarationParser()
        pdecl = ParamParser()

        for l in lines:
            # read tail of function/method declaration if needed
            if not fdecl.isready():
                fdecl.append(ll)
                if fdecl.isready():
                    self.add_new_fdecl(func, fdecl)
                continue

179 180
            # continue capture seealso
            if capturing_seealso:
181
                if not l or l.startswith(" "):
182 183 184
                    seealso = func.get("seealso",[])
                    seealso.extend(l.split(","))
                    func["seealso"] = seealso
185 186
                    continue
                else:
187
                    capturing_seealso = False
188

189 190 191 192 193 194 195 196 197 198 199 200
            ll = l.strip()
            if ll == "..":
                expected_brief = False
                skip_code_lines = False
                continue

            # skip lines if line-skipping mode is activated
            if skip_code_lines:
                if not l or l.startswith(" "):
                    continue
                else:
                    skip_code_lines = False
201

202
            if ll.startswith(".. code-block::") or ll.startswith(".. image::"):
203 204
                skip_code_lines = True
                continue
205

206 207 208 209
            # todo: parse structure members; skip them for now
            if ll.startswith(".. ocv:member::"):
                skip_code_lines = True
                continue
210

211 212 213
            #ignore references (todo: collect them)
            if l.startswith(".. ["):
                continue
214

215 216 217 218 219 220
            if ll.startswith(".. "):
                expected_brief = False
            elif ll.endswith("::"):
                # turn on line-skipping mode for code fragments
                skip_code_lines = True
                ll = ll[:len(ll)-2]
221

222 223 224 225 226 227 228 229
            # continue param parsing (process params after processing .. at the beginning of the line and :: at the end)
            if pdecl.active:
                pdecl.append(l)
                if pdecl.active:
                    continue
                else:
                    self.add_new_pdecl(func, pdecl)
                    # do not continue - current line can contain next parameter definition
230

231 232 233 234 235 236 237 238 239 240 241 242 243
            # parse ".. seealso::" blocks
            if ll.startswith(".. seealso::"):
                if ll.endswith(".. seealso::"):
                    capturing_seealso = True
                else:
                    seealso = func.get("seealso",[])
                    seealso.extend(ll[ll.find("::")+2:].split(","))
                    func["seealso"] = seealso
                continue

            # skip ".. index::"
            if ll.startswith(".. index::"):
                continue
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
            # parse class & struct definitions
            if ll.startswith(".. ocv:class::"):
                func["class"] = ll[ll.find("::")+2:].strip()
                if "method" in func:
                    del func["method"]
                func["isclass"] = True
                expected_brief = True
                continue

            if ll.startswith(".. ocv:struct::"):
                func["class"] = ll[ll.find("::")+2:].strip()
                if "method" in func:
                    del func["method"]
                func["isstruct"] = True
                expected_brief = True
                continue

            # parse function/method definitions
            if fdecl.hasDeclaration(ll):
                fdecl = DeclarationParser(ll)
                if fdecl.isready():
                    self.add_new_fdecl(func, fdecl)
                continue

            # parse parameters
            if pdecl.hasDeclaration(l):
                pdecl = ParamParser(l)
                continue

            # record brief description
            if expected_brief:
                func["brief"] = func.get("brief", "") + "\n" + ll
                if skip_code_lines:
278
                    expected_brief = False # force end brief if code block begins
279 280 281 282
                continue

            # record other lines as long description
            func["long"] = func.get("long", "") + "\n" + ll
283 284
            if skip_code_lines:
                func["long"] = func.get("long", "") + "\n"
285
        # endfor l in lines
286

287
        if fdecl.balance != 0:
288
            if show_critical_errors:
289
                print >> sys.stderr, "RST parser error E%03d: invalid parentheses balance in \"%s\" at %s:%s" % (ERROR_003_PARENTHESES, section_name, file_name, lineno)
290
            return
291 292 293 294 295 296 297 298 299

        # save last parameter if needed
        if pdecl.active:
            self.add_new_pdecl(func, pdecl)

        # add definition to list
        func = self.normalize(func)
        if self.validate(func):
            self.definitions[func["name"]] = func
300
            self.sections_parsed += 1
301 302
            if verbose:
                self.print_info(func)
303
        elif func:
304 305
            if show_errors:
                self.print_info(func, True, sys.stderr)
306

307
    def parse_rst_file(self, module_name, doc):
308 309
        doc = os.path.abspath(doc)
        lineno = 0
310 311
        whitespace_warnings = 0
        max_whitespace_warnings = 10
312

313 314 315 316 317 318 319 320
        lines = []
        flineno = 0
        fname = ""
        prev_line = None

        df = open(doc, "rt")
        for l in df.readlines():
            lineno += 1
321 322 323
            # handle tabs
            if l.find("\t") >= 0:
                whitespace_warnings += 1
324
                if whitespace_warnings <= max_whitespace_warnings and show_warnings:
325
                    print >> sys.stderr, "RST parser warning W%03d: tab symbol instead of space is used at %s:%s" % (WARNING_004_TABS, doc, lineno)
326
                l = l.replace("\t", "    ")
327

328
            # handle first line
329 330 331
            if prev_line == None:
                prev_line = l.rstrip()
                continue
332

333 334
            ll = l.rstrip()
            if len(prev_line) > 0 and len(ll) >= len(prev_line) and ll == "-" * len(ll):
335
                # new function candidate
336
                if len(lines) > 1:
337
                    self.parse_section_safe(module_name, fname, doc, flineno, lines[:len(lines)-1])
338 339 340 341
                lines = []
                flineno = lineno-1
                fname = prev_line.strip()
            elif flineno > 0:
342
                lines.append(ll)
343 344 345
            prev_line = ll
        df.close()

346
        # don't forget about the last function section in file!!!
347
        if len(lines) > 1:
348
            self.parse_section_safe(module_name, fname, doc, flineno, lines)
349 350 351 352 353 354 355 356 357

    def parse_namespace(self, func, section_name):
        known_namespaces = ["cv", "gpu", "flann"]
        l = section_name.strip()
        for namespace in known_namespaces:
            if l.startswith(namespace + "::"):
                func["namespace"] = namespace
                return l[len(namespace)+2:]
        return section_name
358 359

    def add_new_fdecl(self, func, decl):
360 361 362
        if decl.fdecl.endswith(";"):
            print >> sys.stderr, "RST parser error E%03d: unexpected semicolon at the end of declaration in \"%s\" at %s:%s" \
                        % (ERROR_011_EOLEXPECTED, func["name"], func["file"], func["line"])
363 364 365
        decls =  func.get("decls",[])
        if (decl.lang == "C++" or decl.lang == "C"):
            rst_decl = self.cpp_parser.parse_func_decl_no_wrap(decl.fdecl)
366
            decls.append( [decl.lang, decl.fdecl, rst_decl] )
367
        else:
368
            decls.append( [decl.lang, decl.fdecl] )
369 370 371 372 373
        func["decls"] = decls

    def add_new_pdecl(self, func, decl):
        params =  func.get("params",{})
        if decl.name in params:
374
            if show_errors:
375 376
                #check black_list
                if decl.name not in params_blacklist.get(func["name"], []):
377 378
                    print >> sys.stderr, "RST parser error E%03d: redefinition of parameter \"%s\" in \"%s\" at %s:%s" \
                        % (ERROR_005_REDEFENITIONPARAM, decl.name, func["name"], func["file"], func["line"])
379 380 381 382
        else:
            params[decl.name] = decl.comment
            func["params"] = params

383 384
    def print_info(self, func, skipped=False, out = sys.stdout):
        print >> out
385
        if skipped:
386 387
            print >> out, "SKIPPED DEFINITION:"
        print >> out, "name:      %s" % (func.get("name","~empty~"))
388
        print >> out, "file:      %s:%s" % (func.get("file","~empty~"), func.get("line","~empty~"))
389 390 391 392 393 394 395
        print >> out, "is class:  %s" % func.get("isclass",False)
        print >> out, "is struct: %s" % func.get("isstruct",False)
        print >> out, "module:    %s" % func.get("module","~unknown~")
        print >> out, "namespace: %s" % func.get("namespace", "~empty~")
        print >> out, "class:     %s" % (func.get("class","~empty~"))
        print >> out, "method:    %s" % (func.get("method","~empty~"))
        print >> out, "brief:     %s" % (func.get("brief","~empty~"))
396
        if "decls" in func:
397
            print >> out, "declarations:"
398
            for d in func["decls"]:
399
               print >> out, "     %7s: %s" % (d[0], re.sub(r"[ ]+", " ", d[1]))
400
        if "seealso" in func:
401
            print >> out, "seealso:  ", func["seealso"]
402
        if "params" in func:
403
            print >> out, "parameters:"
404
            for name, comment in func["params"].items():
405 406 407
                print >> out, "%23s:   %s" % (name, comment)
        print >> out, "long:      %s" % (func.get("long","~empty~"))
        print >> out
408 409 410

    def validate(self, func):
        if func.get("decls",None) is None:
411 412
            if not func.get("isclass",False) and not func.get("isstruct",False):
                return False
413
        if func["name"] in self.definitions:
414
            if show_errors:
415 416
                print >> sys.stderr, "RST parser error E%03d: \"%s\" from: %s:%s is already documented at %s:%s" \
                    % (ERROR_006_REDEFENITIONFUNC, func["name"], func["file"], func["line"], self.definitions[func["name"]]["file"], self.definitions[func["name"]]["line"])
417
            return False
418 419 420 421 422
        return self.validateParams(func)

    def validateParams(self, func):
        documentedParams = func.get("params",{}).keys()
        params = []
423

424 425 426 427 428 429 430 431 432 433 434
        for decl in func.get("decls", []):
            if len(decl) > 2:
                args = decl[2][3] # decl[2] -> [ funcname, return_ctype, [modifiers], [args] ]
                for arg in args:
                    # arg -> [ ctype, name, def val, [mod], argno ]
                    if arg[0] != "...":
                        params.append(arg[1])
        params = list(set(params))#unique

        # 1. all params are documented
        for p in params:
435
            if p not in documentedParams and show_warnings:
436
                print >> sys.stderr, "RST parser warning W%03d: parameter \"%s\" of \"%s\" is undocumented. %s:%s" % (WARNING_007_UNDOCUMENTEDPARAM, p, func["name"], func["file"], func["line"])
437 438 439

        # 2. only real params are documented
        for p in documentedParams:
440
            if p not in params and show_warnings:
441
                if p not in params_blacklist.get(func["name"], []):
442
                    print >> sys.stderr, "RST parser warning W%03d: unexisting parameter \"%s\" of \"%s\" is documented at %s:%s" % (WARNING_008_MISSINGPARAM, p, func["name"], func["file"], func["line"])
443 444 445 446 447
        return True

    def normalize(self, func):
        if not func:
            return func
448 449 450 451 452 453
        fnname = func["name"]
        fnname = self.normalizeText(fnname)
        fnname = re.sub(r'_\?D$', "_nD", fnname)  # tailing _?D can be mapped to _nD
        fnname = re.sub(r'\?D$', "ND", fnname)  # tailing ?D can be mapped to ND
        fnname = re.sub(r'\(s\)$', "s", fnname) # tailing (s) can be mapped to s
        func["name"] = fnname
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
        if "method" in func:
            func["method"] = self.normalizeText(func["method"])
        if "class" in func:
            func["class"] = self.normalizeText(func["class"])
        if "brief" in func:
            func["brief"] = self.normalizeText(func.get("brief",None))
            if not func["brief"]:
                del func["brief"]
        if "long" in func:
            func["long"] = self.normalizeText(func.get("long",None))
            if not func["long"]:
                del func["long"]
        if "decls" in func:
            func["decls"].sort()
        if "params" in func:
            params = {}
            for name, comment in func["params"].items():
                cmt = self.normalizeText(comment)
                if cmt:
                    params[name] = cmt
474 475 476 477 478
            # expand some wellknown params
            pmap = params_mapping.get(fnname)
            if pmap:
                for name, alias in pmap.items():
                    params[name] = params[alias]
479
            func["params"] = params
480 481 482 483
        if "seealso" in func:
            seealso = []
            for see in func["seealso"]:
                item = self.normalizeText(see.rstrip(".")).strip("\"")
484
                if item and (item.find(" ") < 0 or item.find("::operator") > 0):
485 486
                    seealso.append(item)
            func["seealso"] = list(set(seealso))
487 488
            if not func["seealso"]:
                del func["seealso"]
489 490 491 492

        # special case for old C functions - section name should omit "cv" prefix
        if not func.get("isclass",False) and not func.get("isstruct",False):
            self.fixOldCFunctionName(func)
493 494
        return func

495
    def fixOldCFunctionName(self, func):
496
        if not "decls" in func:
497 498 499 500 501 502 503 504 505 506 507
            return
        fname = None
        for decl in func["decls"]:
            if decl[0] != "C" and decl[0] != "Python1":
                return
            if decl[0] == "C":
                fname = decl[2][0]
        if fname is None:
            return

        fname = fname.replace(".", "::")
508
        if fname.startswith("cv::cv"):
509
            if fname[6:] == func.get("name", "").replace("*", "_n"):
510 511
                func["name"] = fname[4:]
                func["method"] = fname[4:]
512
            elif show_warnings:
513
                print >> sys.stderr, "RST parser warning W%03d:  \"%s\" - section name is \"%s\" instead of \"%s\" at %s:%s" % (WARNING_009_HDRMISMATCH, fname, func["name"], fname[6:], func["file"], func["line"])
514
                #self.print_info(func)
515

516 517 518
    def normalizeText(self, s):
        if s is None:
            return s
V
Vadim Pisarevsky 已提交
519 520

        s = re.sub(r"\.\. math::[ \r]*\n+((.|\n)*?)(\n[ \r]*\n|$)", mathReplace2, s)
521 522
        s = re.sub(r":math:`([^`]+?)`", mathReplace, s)
        s = re.sub(r" *:sup:", "^", s)
523

524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
        s = s.replace(":ocv:class:", "")
        s = s.replace(":ocv:struct:", "")
        s = s.replace(":ocv:func:", "")
        s = s.replace(":ocv:cfunc:","")
        s = s.replace(":c:type:", "")
        s = s.replace(":c:func:", "")
        s = s.replace(":ref:", "")
        s = s.replace(":math:", "")
        s = s.replace(":func:", "")

        s = s.replace("]_", "]")
        s = s.replace(".. note::", "Note:")
        s = s.replace(".. table::", "")
        s = s.replace(".. ocv:function::", "")
        s = s.replace(".. ocv:cfunction::", "")

        # remove ".. identifier:" lines
        s = re.sub(r"(^|\n)\.\. [a-zA-Z_0-9]+(::[a-zA-Z_0-9]+)?:(\n|$)", "\n ", s)
        # unwrap urls
        s = re.sub(r"`([^`<]+ )<(https?://[^>]+)>`_", "\\1(\\2)", s)
        # remove tailing ::
        s = re.sub(r"::(\n|$)", "\\1", s)
546

547 548 549
        # normalize line endings
        s = re.sub(r"\r\n", "\n", s)
        # remove extra line breaks before/after _ or ,
550
        s = re.sub(r"\n[ ]*([_,])\n", r"\1 ", s)
551 552
        # remove extra line breaks after `
        #s = re.sub(r"`\n", "` ", s)
553
        # remove extra space after ( and before .,)
554
        s = re.sub(r"\([\n ]+", "(", s)
555
        s = re.sub(r"[\n ]+(\.|,|\))", "\\1", s)
556 557
        # remove extra line breaks after ".. note::"
        s = re.sub(r"\.\. note::\n+", ".. note:: ", s)
558
        # remove extra line breaks before *
559
        s = re.sub(r"\n+\*", "\n*", s)
560 561
        # remove extra line breaks after *
        s = re.sub(r"\n\*\n+", "\n* ", s)
562
        # remove extra line breaks before #.
563
        s = re.sub(r"\n+#\.", "\n#.", s)
564
        # remove extra line breaks after #.
565
        s = re.sub(r"\n#\.\n+", "\n#. ", s)
566
        # remove extra line breaks before `
567
        #s = re.sub(r"\n[ ]*`", " `", s)
568
        # remove trailing whitespaces
569
        s = re.sub(r"[ ]+$", "", s)
570
        # remove .. for references
571
        #s = re.sub(r"\.\. \[", "[", s)
572 573
        # unescape
        s = re.sub(r"\\(.)", "\\1", s)
574

575 576 577 578 579 580 581 582 583
        # remove whitespace before .
        s = re.sub(r"[ ]+\.", ".", s)
        # remove tailing whitespace
        s = re.sub(r" +(\n|$)", "\\1", s)
        # remove leading whitespace
        s = re.sub(r"(^|\n) +", "\\1", s)
        # compress line breaks
        s = re.sub(r"\n\n+", "\n\n", s)
        # remove other newlines
584
        s = re.sub(r"([^.\n\\=])\n([^*#\n]|\*[^ ])", "\\1 \\2", s)
585 586 587
        # compress whitespace
        s = re.sub(r" +", " ", s)

588 589 590
        # restore math
        s = re.sub(r" *<BR> *","\n", s)

591 592 593 594
        # remove extra space before .
        s = re.sub(r"[\n ]+\.", ".", s)

        s = s.replace("**", "")
V
Vadim Pisarevsky 已提交
595
        s = re.sub(r"``([^\n]+?)``", "<code>\\1</code>", s)
596 597 598 599
        s = s.replace("``", "\"")
        s = s.replace("`", "\"")
        s = s.replace("\"\"", "\"")

600 601
        s = s.strip()
        return s
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
    def printSummary(self):
        print
        print "RST Parser Summary:"
        print "  Total sections:   %s" % self.sections_total
        print "  Skipped sections: %s" % self.sections_skipped
        print "  Parsed  sections: %s" % self.sections_parsed
        print "  Invalid sections: %s" % (self.sections_total - self.sections_parsed - self.sections_skipped)

        # statistic by language
        stat = {}
        classes = 0
        structs = 0
        for name, d in self.definitions.items():
           if d.get("isclass", False):
               classes += 1
           elif d.get("isstruct", False):
               structs += 1
           else:
               for decl in d.get("decls",[]):
                   stat[decl[0]] = stat.get(decl[0],0) + 1

        print
        print "  classes documented:           %s" % classes
        print "  structs documented:           %s" % structs
        for lang in sorted(stat.items()):
            print "  %7s functions documented: %s" % lang
629
        print
K
Kirill Kornyakov 已提交
630

631 632 633 634 635 636 637 638 639 640 641 642
def mathReplace2(match):
    m = mathReplace(match)
    #print "%s   ===>   %s" % (match.group(0), m)
    return "\n\n"+m+"<BR><BR>"

def hdotsforReplace(match):
    return '...  '*int(match.group(1))

def matrixReplace(match):
    m = match.group(2)
    m = re.sub(r" *& *", "   ", m)
    return m
643

644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
def mathReplace(match):
    m = match.group(1)

    m = m.replace("\n", "<BR>")
    m = re.sub(r"\\text(tt|rm)?{(.*?)}", "\\2", m)
    m = re.sub(r"\\mbox{(.*?)}", "\\1", m)
    m = re.sub(r"\\mathrm{(.*?)}", "\\1", m)
    m = re.sub(r"\\vecthree{(.*?)}{(.*?)}{(.*?)}", "[\\1 \\2 \\3]", m)
    m = re.sub(r"\\bar{(.*?)}", "\\1`", m)
    m = re.sub(r"\\sqrt\[(\d)*\]{(.*?)}", "sqrt\\1(\\2)", m)
    m = re.sub(r"\\sqrt{(.*?)}", "sqrt(\\1)", m)
    m = re.sub(r"\\frac{(.*?)}{(.*?)}", "(\\1)/(\\2)", m)
    m = re.sub(r"\\fork{(.*?)}{(.*?)}{(.*?)}{(.*?)}", "\\1 \\2; \\3 \\4", m)
    m = re.sub(r"\\forkthree{(.*?)}{(.*?)}{(.*?)}{(.*?)}{(.*?)}{(.*?)}", "\\1 \\2; \\3 \\4; \\5 \\6", m)
    m = re.sub(r"\\stackrel{(.*?)}{(.*?)}", "\\1 \\2", m)
    m = re.sub(r"\\sum _{(.*?)}", "sum{by: \\1}", m)

    m = re.sub(r" +", " ", m)
    m = re.sub(r"\\begin{(?P<gtype>array|bmatrix)}(?:{[\|lcr\. ]+})? *(.*?)\\end{(?P=gtype)}", matrixReplace, m)
    m = re.sub(r"\\hdotsfor{(\d+)}", hdotsforReplace, m)
    m = re.sub(r"\\vecthreethree{(.*?)}{(.*?)}{(.*?)}{(.*?)}{(.*?)}{(.*?)}{(.*?)}{(.*?)}{(.*?)}", "<BR>|\\1 \\2 \\3|<BR>|\\4 \\5 \\6|<BR>|\\7 \\8 \\9|<BR>", m)
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
    m = re.sub(r"\\left[ ]*\\lfloor[ ]*", "[", m)
    m = re.sub(r"[ ]*\\right[ ]*\\rfloor", "]", m)
    m = re.sub(r"\\left[ ]*\([ ]*", "(", m)
    m = re.sub(r"[ ]*\\right[ ]*\)", ")", m)
    m = re.sub(r"([^\\])\$", "\\1", m)

    m = m.replace("\\times", "x")
    m = m.replace("\\pm", "+-")
    m = m.replace("\\cdot", "*")
    m = m.replace("\\sim", "~")
    m = m.replace("\\leftarrow", "<-")
    m = m.replace("\\rightarrow", "->")
    m = m.replace("\\leftrightarrow", "<->")
    m = re.sub(r" *\\neg *", " !", m)
    m = re.sub(r" *\\neq? *", " != ", m)
    m = re.sub(r" *\\geq? *", " >= ", m)
    m = re.sub(r" *\\leq? *", " <= ", m)
    m = re.sub(r" *\\vee *", " V ", m)
    m = re.sub(r" *\\oplus *", " (+) ", m)
    m = re.sub(r" *\\mod *", " mod ", m)
    m = re.sub(r"( *)\\partial *", "\\1d", m)

    m = re.sub(r"( *)\\quad *", "\\1 ", m)
    m = m.replace("\\,", " ")
    m = m.replace("\\:", "  ")
    m = m.replace("\\;", "   ")
    m = m.replace("\\!", "")

    m = m.replace("\\\\", "<BR>")
    m = m.replace("\\wedge", "/\\\\")
    m = re.sub(r"\\(.)", "\\1", m)

    m = re.sub(r"\([ ]+", "(", m)
    m = re.sub(r"[ ]+(\.|,|\))(<BR>| |$)", "\\1\\2", m)
    m = re.sub(r" +\|[ ]+([a-zA-Z0-9_(])", " |\\1", m)
    m = re.sub(r"([a-zA-Z0-9_)}])[ ]+(\(|\|)", "\\1\\2", m)

    m = re.sub(r"{\((-?[a-zA-Z0-9_]+)\)}", "\\1", m)
    m = re.sub(r"{(-?[a-zA-Z0-9_]+)}", "(\\1)", m)
    m = re.sub(r"\(([0-9]+)\)", "\\1", m)
    m = m.replace("{", "(")
    m = m.replace("}", ")")

    #print "%s   ===>   %s" % (match.group(0), m)
V
Vadim Pisarevsky 已提交
710
    return "<em>" + m + "</em>"
711 712

if __name__ == "__main__":
713
    if len(sys.argv) < 2:
714 715
        print "Usage:\n", os.path.basename(sys.argv[0]), " <module path>"
        exit(0)
716

717 718 719
    if len(sys.argv) >= 3:
        if sys.argv[2].lower() == "verbose":
            verbose = True
720 721

    rst_parser_dir  = os.path.dirname(os.path.abspath(sys.argv[0]))
A
Andrey Pavlenko 已提交
722
    hdr_parser_path = os.path.join(rst_parser_dir, "../../python/src2")
723 724 725 726

    sys.path.append(hdr_parser_path)
    import hdr_parser

K
Kirill Kornyakov 已提交
727
    module = sys.argv[1]
728

A
Andrey Pavlenko 已提交
729
    if module != "all" and not os.path.isdir(os.path.join(rst_parser_dir, "../../" + module)):
730
        print "RST parser error E%03d: module \"%s\" could not be found." % (ERROR_010_NOMODULE, module)
731 732 733
        exit(1)

    parser = RstParser(hdr_parser.CppHeaderParser())
734

735
    if module == "all":
736
        for m in allmodules:
A
Andrey Pavlenko 已提交
737
            parser.parse(m, os.path.join(rst_parser_dir, "../../" + m))
738
    else:
A
Andrey Pavlenko 已提交
739
        parser.parse(module, os.path.join(rst_parser_dir, "../../" + module))
740 741

    # summary
742
    parser.printSummary()