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