File indexing completed on 2024-04-14 05:34:18
0001 #!/usr/bin/env python3 0002 0003 import argparse 0004 import codecs 0005 import logging 0006 import os 0007 import re 0008 import sys 0009 0010 import doxyqml.qmlparser as qmlparser 0011 0012 from doxyqml import __version__, DESCRIPTION 0013 from doxyqml.lexer import Lexer, LexerError 0014 from doxyqml.qmlclass import QmlClass 0015 0016 0017 def coord_for_idx(text, idx): 0018 head, sep, tail = text[:idx].rpartition("\n") 0019 if sep == "\n": 0020 row = head.count("\n") + 2 0021 else: 0022 row = 1 0023 col = len(tail) + 1 0024 return row, col 0025 0026 0027 def line_for_idx(text, idx): 0028 bol = text.rfind("\n", 0, idx) 0029 if bol == -1: 0030 bol = 0 0031 eol = text.find("\n", idx) 0032 return text[bol:eol] 0033 0034 0035 def info_for_error_at(text, idx): 0036 row, col = coord_for_idx(text, idx) 0037 line = line_for_idx(text, idx) 0038 msg = line + "\n" + "-" * (col - 1) + "^" 0039 return row, msg 0040 0041 0042 def parse_args(argv): 0043 parser = argparse.ArgumentParser( 0044 prog="doxyqml", 0045 description=DESCRIPTION, 0046 ) 0047 parser.add_argument("-d", "--debug", 0048 action="store_true", 0049 help="Log debug info to stderr") 0050 parser.add_argument("--namespace", 0051 action='append', 0052 default=[], 0053 help="Wrap the generated C++ classes in NAMESPACE") 0054 parser.add_argument("--no-since-version", 0055 action="store_true", 0056 default=False, 0057 help="Don't append \"Since: [version]\" info to docstring") 0058 parser.add_argument('--no-nested-components', 0059 action='store_true', 0060 default=False, 0061 help="Don't create private member documentation for nested components") 0062 parser.add_argument('--version', 0063 action='version', 0064 version='%%(prog)s %s' % __version__) 0065 parser.add_argument("qml_file", 0066 help="The QML file to parse") 0067 0068 return parser.parse_args(argv) 0069 0070 0071 def find_qmldir_file(qml_file): 0072 dir = os.path.dirname(qml_file) 0073 0074 while True: 0075 # Check if `dir` contains a file of the name "qmldir". 0076 name = os.path.join(dir, 'qmldir') 0077 0078 if os.path.isfile(name): 0079 return name 0080 0081 # Pick parent of `dir`. Abort once parent stops changing, 0082 # either because we reached the root directory, or because 0083 # relative paths were used and we reached the currrent 0084 # working directory. 0085 parent = os.path.dirname(dir) 0086 0087 if parent == dir: 0088 return None 0089 0090 dir = parent 0091 0092 0093 def find_classname(qml_file, namespace=None): 0094 classname = os.path.basename(qml_file).split(".")[0] 0095 classversion = None 0096 modulename = '' 0097 0098 qmldir = find_qmldir_file(qml_file) 0099 0100 if qmldir: 0101 text = open(qmldir).read() 0102 match = re.match(r'^module\s+((?:\w|\.)+)\s*$', text, re.MULTILINE) 0103 0104 if match: 0105 modulename = match.group(1) 0106 0107 basedir = os.path.dirname(qmldir) 0108 0109 rx_object_type = re.compile(r'^(\w+)\s+(\d+(?:\.\d+)*)\s+(\S+)\s*$', re.MULTILINE) 0110 0111 # skip internal classes 0112 if "internal" in text: 0113 return None, None, None 0114 0115 for name, version, path in rx_object_type.findall(text): 0116 filename = os.path.join(basedir, path) 0117 0118 if os.path.isfile(filename) and os.path.samefile(qml_file, filename): 0119 classversion = version 0120 classname = name 0121 break 0122 0123 if modulename: 0124 classname = modulename + '.' + classname 0125 0126 if namespace: 0127 classname = '.'.join(namespace) + '.' + classname 0128 0129 return classname, classversion, modulename 0130 0131 0132 def main(argv=None, out=None): 0133 if argv is None: 0134 argv = sys.argv[1:] 0135 if out is None: 0136 out = sys.stdout 0137 0138 args = parse_args(argv) 0139 0140 name = args.qml_file 0141 namespace = args.namespace 0142 0143 encoding = "utf-8" 0144 first_4_bytes = open(name, 'rb').read(4) 0145 if (first_4_bytes.startswith(codecs.BOM_UTF8)): 0146 encoding = "utf-8-sig" 0147 text = open(name, encoding=encoding).read() 0148 0149 lexer = Lexer(text) 0150 try: 0151 lexer.tokenize() 0152 except LexerError as exc: 0153 logging.error("Failed to tokenize %s" % name) 0154 row, msg = info_for_error_at(text, exc.idx) 0155 logging.error("Lexer error line %d: %s\n%s", row, exc, msg) 0156 if args.debug: 0157 raise 0158 else: 0159 return -1 0160 0161 if args.debug: 0162 for token in lexer.tokens: 0163 print("%20s %s" % (token.type, token.value)) 0164 0165 classname, classversion, modulename = find_classname(name, namespace) 0166 if args.no_since_version: 0167 classversion = None 0168 0169 if classname is None: 0170 return 0171 0172 qml_class = QmlClass(classname, classversion, modulename, not args.no_nested_components) 0173 0174 try: 0175 qmlparser.parse(lexer.tokens, qml_class, not args.no_nested_components) 0176 except qmlparser.QmlParserError as exc: 0177 logging.error("Failed to parse %s" % name) 0178 row, msg = info_for_error_at(text, exc.token.idx) 0179 logging.error("Lexer error line %d: %s\n%s", row, exc, msg) 0180 if args.debug: 0181 raise 0182 else: 0183 return -1 0184 0185 out = codecs.getwriter("utf-8")(out.buffer) 0186 print(qml_class, file=out) 0187 0188 return 0 0189 0190 0191 if __name__ == "__main__": 0192 sys.exit(main()) 0193 # vi: ts=4 sw=4 et