File indexing completed on 2024-04-21 03:52:27
0001 # -*- coding: utf-8 -*- 0002 # 0003 # SPDX-FileCopyrightText: 2016 Olivier Churlaud <olivier@churlaud.com> 0004 # SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kdemail.net> 0005 # SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org> 0006 # SPDX-FileCopyrightText: 2014 Alex Turbov <i.zaufi@gmail.com> 0007 # 0008 # SPDX-License-Identifier: BSD-2-Clause 0009 0010 import logging 0011 import os 0012 import sys 0013 from typing import Any, Dict, Optional 0014 0015 from urllib.request import Request, urlopen 0016 from urllib.error import HTTPError 0017 0018 import yaml 0019 0020 from kapidox import utils 0021 from kapidox.models import Library, Product 0022 0023 __all__ = ( 0024 "create_metainfo", 0025 "parse_tree") 0026 0027 PLATFORM_ALL = "All" 0028 PLATFORM_UNKNOWN = "UNKNOWN" 0029 0030 0031 ## @package kapidox.preprocessing 0032 # 0033 # Preprocessing of the needed information. 0034 # 0035 # The module allow to walk through folders, read metainfo files and create 0036 # products, subgroups and libraries representing the projects. 0037 # 0038 0039 def expand_platform_all(dct, available_platforms): 0040 """If one of the keys of dct is `PLATFORM_ALL` (or `PLATFORM_UNKNOWN`), 0041 remove it and add entries for all available platforms to dct 0042 0043 Args: 0044 dct: (dictionary) dictionary to expand 0045 available_platforms: (list of string) name of platforms 0046 """ 0047 0048 add_all_platforms = False 0049 if PLATFORM_ALL in dct: 0050 note = dct[PLATFORM_ALL] 0051 add_all_platforms = True 0052 del dct[PLATFORM_ALL] 0053 if PLATFORM_UNKNOWN in dct: 0054 add_all_platforms = True 0055 note = dct[PLATFORM_UNKNOWN] 0056 del dct[PLATFORM_UNKNOWN] 0057 if add_all_platforms: 0058 for platform in available_platforms: 0059 if platform not in dct: 0060 dct[platform] = note 0061 0062 0063 def create_metainfo(path) -> Optional[Dict[str, Any]]: 0064 """Look for a `metadata.yaml` file and create a dictionary out it. 0065 0066 Args: 0067 path: (string) the current path to search. 0068 Returns: 0069 A dictionary containing all the parsed information, or `None` if it 0070 did not fulfill some conditions. 0071 """ 0072 0073 metainfo: Optional[Dict[str, Any]] 0074 0075 if not os.path.isdir(path): 0076 return None 0077 0078 try: 0079 metainfo_file = os.path.join(path, 'metainfo.yaml') 0080 except UnicodeDecodeError as e: 0081 logging.warning('Unusual base path {!r} for metainfo.yaml'.format(path)) 0082 return None 0083 if not os.path.isfile(metainfo_file): 0084 return None 0085 0086 try: 0087 metainfo = yaml.safe_load(open(metainfo_file)) 0088 except Exception as e: 0089 print(e) 0090 logging.warning(f'Could not load metainfo.yaml for {path}, skipping it') 0091 return None 0092 0093 if metainfo is None: 0094 logging.warning(f'Empty metainfo.yaml for {path}, skipping it') 0095 return None 0096 0097 if 'subgroup' in metainfo and 'group' not in metainfo: 0098 logging.warning(f'Subgroup but no group in {path}, skipping it') 0099 return None 0100 0101 # Suppose we get a relative path passed in (e.g. on the command-line, 0102 # path .. because we're building the dox in a subdirectory of a source 0103 # checkout) then we don't want dirname to be "..", but the name that 0104 # that resolves to. 0105 dirname = os.path.basename(os.path.abspath(path)) 0106 if 'fancyname' in metainfo: 0107 fancyname = metainfo['fancyname'] 0108 else: 0109 fancyname = utils.parse_fancyname(path) 0110 0111 if not fancyname: 0112 logging.warning(f'Could not find fancy name for {path}, skipping it') 0113 return None 0114 # A fancyname has 1st char capitalized 0115 fancyname = fancyname[0].capitalize() + fancyname[1:] 0116 0117 if 'repo_id' in metainfo: 0118 repo_id = metainfo['repo_id'] 0119 else: 0120 repo_id = dirname 0121 0122 qdoc: bool = False 0123 0124 if 'qdoc' in metainfo: 0125 qdoc = metainfo['qdoc'] 0126 0127 metainfo.update({ 0128 'fancyname': fancyname, 0129 'name': dirname, 0130 'repo_id': repo_id, 0131 'public_lib': metainfo.get('public_lib', False), 0132 'dependency_diagram': None, 0133 'path': path, 0134 'qdoc': qdoc, 0135 }) 0136 0137 # replace legacy platform names 0138 if 'platforms' in metainfo: 0139 platforms = metainfo['platforms'] 0140 for index, x in enumerate(platforms): 0141 if x['name'] == "MacOSX": 0142 x['name'] = "macOS" 0143 platforms[index] = x 0144 logging.warning('{fancyname} uses outdated platform name, please replace "MacOSX" with "macOS".' 0145 .format_map(metainfo)) 0146 metainfo.update({'platforms': platforms}) 0147 if 'group_info' in metainfo: 0148 group_info = metainfo['group_info'] 0149 if 'platforms' in group_info: 0150 platforms = group_info['platforms'] 0151 for index, x in enumerate(platforms): 0152 if "MacOSX" in x: 0153 x = x.replace("MacOSX", "macOS") 0154 platforms[index] = x 0155 logging.warning('Group {fancyname} uses outdated platform name, please replace "MacOSX" with "macOS".' 0156 .format_map(group_info)) 0157 group_info.update({'platforms': platforms}) 0158 0159 return metainfo 0160 0161 0162 def parse_tree(rootdir): 0163 """Recursively call create_metainfo() in subdirs of rootdir 0164 0165 Args: 0166 rootdir: (string) Top level directory containing the libraries. 0167 0168 Returns: 0169 A list of metainfo dictionary (see create_metainfo()). 0170 0171 """ 0172 metalist = [] 0173 for path, dirs, _ in os.walk(rootdir, topdown=True): 0174 # We don't want to do the recursion in the dotdirs 0175 dirs[:] = [d for d in dirs if not d[0] == '.'] 0176 metainfo = create_metainfo(path) 0177 if metainfo is not None: 0178 # There was a metainfo.yaml, which means it was 0179 # the top of a checked-out repository. Stop processing, 0180 # because we do not support having repo B checked out (even 0181 # as a submodule) inside repo A. 0182 # 0183 # There are exceptions: messagelib (KDE PIM) contains 0184 # multiple subdirectories with their own metainfo.yaml, 0185 # which are listed as public sources. 0186 dirs[:] = [d for d in dirs if d in metainfo.get('public_source_dirs', [])] 0187 if metainfo['public_lib'] or 'group_info' in metainfo: 0188 metalist.append(metainfo) 0189 else: 0190 logging.warning('{name} has no public libraries'.format_map(metainfo)) 0191 0192 return metalist 0193 0194 0195 def sort_metainfo(metalist, all_maintainers): 0196 """Extract the structure (Product/Subproduct/Library) from the metainfo 0197 list. 0198 0199 Args: 0200 metalist: (list of dict) lists of the metainfo extracted in parse_tree(). 0201 all_maintainers: (dict of dict) all possible maintainers. 0202 0203 Returns: 0204 A list of Products, a list of groups (which are products containing 0205 several libraries), a list of Libraries and the available platforms. 0206 """ 0207 products = dict() 0208 groups = [] 0209 libraries = [] 0210 available_platforms = {'Windows', 'macOS', 'Linux', 'Android', 'FreeBSD'} 0211 0212 # First extract the structural info 0213 for metainfo in metalist: 0214 product = extract_product(metainfo, all_maintainers) 0215 if product is not None: 0216 products[product.name] = product 0217 0218 # Second extract the libraries 0219 for metainfo in metalist: 0220 try: 0221 platforms = metainfo['platforms'] 0222 platform_lst = [x['name'] for x in platforms 0223 if x['name'] not in (PLATFORM_ALL, 0224 PLATFORM_UNKNOWN)] 0225 0226 available_platforms.update(set(platform_lst)) 0227 except (KeyError, TypeError): 0228 logging.warning('{fancyname} library lacks valid platform definitions' 0229 .format_map(metainfo)) 0230 platforms = [dict(name=PLATFORM_UNKNOWN)] 0231 0232 dct = dict((x['name'], x.get('note', '')) for x in platforms) 0233 0234 expand_platform_all(dct, available_platforms) 0235 platforms = dct 0236 0237 if metainfo['public_lib']: 0238 lib = Library(metainfo, products, platforms, all_maintainers) 0239 libraries.append(lib) 0240 0241 groups = [] 0242 for key in products.copy(): 0243 if len(products[key].libraries) == 0: 0244 del products[key] 0245 elif products[key].part_of_group: 0246 groups.append(products[key]) 0247 0248 return list(products.values()), groups, libraries, available_platforms 0249 0250 0251 def extract_product(metainfo, all_maintainers): 0252 """Extract a product from a metainfo dictionary. 0253 0254 Args: 0255 metainfo: (dict) metainfo created by the create_metainfo() function. 0256 all_maintainers: (dict of dict) all possible maintainers 0257 0258 Returns: 0259 A Product or None if the metainfo does not describe a product. 0260 """ 0261 0262 if 'group_info' not in metainfo and 'group' in metainfo: 0263 # This is not a product but a simple lib 0264 return None 0265 0266 try: 0267 product = Product(metainfo, all_maintainers) 0268 return product 0269 except ValueError as e: 0270 logging.error(e) 0271 return None