1import json 2import os 3 4from typing import Any, Dict, List, NamedTuple, Optional 5 6 7JSON = Dict[str, Any] 8 9 10DEFAULT_MAP_FILE = "projects.json" 11 12 13class ProjectInfo(NamedTuple): 14 """ 15 Information about a project to analyze. 16 """ 17 name: str 18 mode: int 19 enabled: bool = True 20 21 22class ProjectMap: 23 """ 24 Project map stores info about all the "registered" projects. 25 """ 26 def __init__(self, path: Optional[str] = None, should_exist: bool = True): 27 """ 28 :param path: optional path to a project JSON file, when None defaults 29 to DEFAULT_MAP_FILE. 30 :param should_exist: flag to tell if it's an exceptional situation when 31 the project file doesn't exist, creates an empty 32 project list instead if we are not expecting it to 33 exist. 34 """ 35 if path is None: 36 path = os.path.join(os.path.abspath(os.curdir), DEFAULT_MAP_FILE) 37 38 if not os.path.exists(path): 39 if should_exist: 40 raise ValueError( 41 f"Cannot find the project map file {path}" 42 f"\nRunning script for the wrong directory?\n") 43 else: 44 self._create_empty(path) 45 46 self.path = path 47 self._load_projects() 48 49 def save(self): 50 """ 51 Save project map back to its original file. 52 """ 53 self._save(self.projects, self.path) 54 55 def _load_projects(self): 56 with open(self.path) as raw_data: 57 raw_projects = json.load(raw_data) 58 59 if not isinstance(raw_projects, list): 60 raise ValueError( 61 "Project map should be a list of JSON objects") 62 63 self.projects = self._parse(raw_projects) 64 65 @staticmethod 66 def _parse(raw_projects: List[JSON]) -> List[ProjectInfo]: 67 return [ProjectMap._parse_project(raw_project) 68 for raw_project in raw_projects] 69 70 @staticmethod 71 def _parse_project(raw_project: JSON) -> ProjectInfo: 72 try: 73 name: str = raw_project["name"] 74 build_mode: int = raw_project["mode"] 75 enabled: bool = raw_project.get("enabled", True) 76 return ProjectInfo(name, build_mode, enabled) 77 78 except KeyError as e: 79 raise ValueError( 80 f"Project info is required to have a '{e.args[0]}' field") 81 82 @staticmethod 83 def _create_empty(path: str): 84 ProjectMap._save([], path) 85 86 @staticmethod 87 def _save(projects: List[ProjectInfo], path: str): 88 with open(path, "w") as output: 89 json.dump(ProjectMap._convert_infos_to_dicts(projects), 90 output, indent=2) 91 92 @staticmethod 93 def _convert_infos_to_dicts(projects: List[ProjectInfo]) -> List[JSON]: 94 return [project._asdict() for project in projects] 95