crytic_compile.utils.naming
Module handling the file naming operation (relative -> absolute, etc)
1""" 2Module handling the file naming operation (relative -> absolute, etc) 3""" 4 5import logging 6import os.path 7import platform 8from dataclasses import dataclass 9from pathlib import Path 10from typing import TYPE_CHECKING, Union, Callable, Optional 11 12from crytic_compile.platform.exceptions import InvalidCompilation 13 14# Cycle dependency 15if TYPE_CHECKING: 16 from crytic_compile import CryticCompile 17 18LOGGER = logging.getLogger("CryticCompile") 19 20 21@dataclass 22class Filename: 23 """Path metadata for each file in the compilation unit""" 24 25 def __init__(self, absolute: str, used: str, relative: str, short: str): 26 self.absolute = absolute 27 self.used = used 28 self.relative = relative 29 self.short = short 30 31 def __hash__(self) -> int: 32 return hash(self.relative) 33 34 def __eq__(self, other: object) -> bool: 35 if not isinstance(other, Filename): 36 return NotImplemented 37 return self.relative == other.relative 38 39 def __repr__(self) -> str: 40 return f"Filename(absolute={self.absolute}, used={self.used}, relative={self.relative}, short={self.short}))" 41 42 43def extract_name(name: str) -> str: 44 """Convert '/path:Contract' to Contract 45 46 Args: 47 name (str): name to convert 48 49 Returns: 50 str: extracted contract name 51 """ 52 return name[name.rfind(":") + 1 :] 53 54 55def extract_filename(name: str) -> str: 56 """Convert '/path:Contract' to /path 57 58 Args: 59 name (str): name to convert 60 61 Returns: 62 str: extracted filename 63 """ 64 if not ":" in name: 65 return name 66 return name[: name.rfind(":")] 67 68 69def combine_filename_name(filename: str, name: str) -> str: 70 """Combine the filename with the contract name 71 72 Args: 73 filename (str): filename 74 name (str): contract name 75 76 Returns: 77 str: Combined names 78 """ 79 return filename + ":" + name 80 81 82def _verify_filename_existence(filename: Path, cwd: Path) -> Path: 83 """ 84 Check if the filename exist. If it does not, try multiple heuristics to find the right filename: 85 - Look for contracts/FILENAME 86 - Look for node_modules/FILENAME 87 - Look for node_modules/FILENAME in all the parents directories 88 89 90 Args: 91 filename (Path): filename to check 92 cwd (Path): directory 93 94 Raises: 95 InvalidCompilation: if the filename is not found 96 97 Returns: 98 Path: the filename 99 """ 100 101 if filename.exists(): 102 return filename 103 104 if cwd.joinpath(Path("contracts"), filename).exists(): 105 filename = cwd.joinpath("contracts", filename) 106 elif cwd.joinpath(filename).exists(): 107 filename = cwd.joinpath(filename) 108 # how node.js loads dependencies from node_modules: 109 # https://nodejs.org/api/modules.html#loading-from-node_modules-folders 110 elif cwd.joinpath(Path("node_modules"), filename).exists(): 111 filename = cwd.joinpath("node_modules", filename) 112 else: 113 for parent in cwd.parents: 114 if parent.joinpath(Path("node_modules"), filename).exists(): 115 filename = parent.joinpath(Path("node_modules"), filename) 116 break 117 118 if not filename.exists(): 119 raise InvalidCompilation(f"Unknown file: {filename}") 120 121 return filename 122 123 124# pylint: disable=too-many-branches 125def convert_filename( 126 used_filename: Union[str, Path], 127 relative_to_short: Callable[[Path], Path], 128 crytic_compile: "CryticCompile", 129 working_dir: Optional[Union[str, Path]] = None, 130) -> Filename: 131 """Convert a filename to CryticCompile Filename object. 132 The used_filename can be absolute, relative, or missing node_modules/contracts directory 133 134 Args: 135 used_filename (Union[str, Path]): Used filename 136 relative_to_short (Callable[[Path], Path]): Callback to translate the relative to short 137 crytic_compile (CryticCompile): Associated CryticCompile object 138 working_dir (Optional[Union[str, Path]], optional): Working directory. Defaults to None. 139 140 Returns: 141 Filename: Filename converted 142 """ 143 filename_txt = used_filename 144 if platform.system() == "Windows": 145 elements = list(Path(filename_txt).parts) 146 if elements[0] == "/" or elements[0] == "\\": 147 elements = elements[1:] # remove '/' 148 elements[0] = elements[0] + ":/" # add :/ 149 filename = Path(*elements) 150 else: 151 filename = Path(filename_txt) 152 153 # cwd points to the directory to be used 154 if working_dir is None: 155 cwd = Path.cwd() 156 else: 157 working_dir = Path(working_dir) 158 if working_dir.is_absolute(): 159 cwd = working_dir 160 else: 161 cwd = Path.cwd().joinpath(Path(working_dir)).resolve() 162 163 if crytic_compile.package_name: 164 try: 165 filename = filename.relative_to(Path(crytic_compile.package_name)) 166 except ValueError: 167 pass 168 169 filename = _verify_filename_existence(filename, cwd) 170 171 absolute = Path(os.path.abspath(filename)) 172 173 # This returns original path if *path* and *start* are on different drives (for Windows platform). 174 try: 175 relative = Path(os.path.relpath(filename, Path.cwd())) 176 except ValueError: 177 relative = Path(filename) 178 179 # Build the short path 180 try: 181 if cwd.is_absolute(): 182 short = absolute.relative_to(cwd) 183 else: 184 short = relative.relative_to(cwd) 185 except ValueError: 186 short = relative 187 except RuntimeError: 188 short = relative 189 190 short = relative_to_short(short) 191 # Starting with v0.8.8 (https://github.com/ethereum/solidity/pull/11545), solc normalizes the paths to not include the drive on Windows, 192 # so it's important we use posix path here to avoid issues with the path comparison. 193 return Filename( 194 absolute=absolute.as_posix(), 195 relative=relative.as_posix(), 196 short=short.as_posix(), 197 used=Path(used_filename).as_posix(), 198 )
23class Filename: 24 """Path metadata for each file in the compilation unit""" 25 26 def __init__(self, absolute: str, used: str, relative: str, short: str): 27 self.absolute = absolute 28 self.used = used 29 self.relative = relative 30 self.short = short 31 32 def __hash__(self) -> int: 33 return hash(self.relative) 34 35 def __eq__(self, other: object) -> bool: 36 if not isinstance(other, Filename): 37 return NotImplemented 38 return self.relative == other.relative 39 40 def __repr__(self) -> str: 41 return f"Filename(absolute={self.absolute}, used={self.used}, relative={self.relative}, short={self.short}))"
Path metadata for each file in the compilation unit
44def extract_name(name: str) -> str: 45 """Convert '/path:Contract' to Contract 46 47 Args: 48 name (str): name to convert 49 50 Returns: 51 str: extracted contract name 52 """ 53 return name[name.rfind(":") + 1 :]
Convert '/path:Contract' to Contract
Args: name (str): name to convert
Returns: str: extracted contract name
56def extract_filename(name: str) -> str: 57 """Convert '/path:Contract' to /path 58 59 Args: 60 name (str): name to convert 61 62 Returns: 63 str: extracted filename 64 """ 65 if not ":" in name: 66 return name 67 return name[: name.rfind(":")]
Convert '/path:Contract' to /path
Args: name (str): name to convert
Returns: str: extracted filename
70def combine_filename_name(filename: str, name: str) -> str: 71 """Combine the filename with the contract name 72 73 Args: 74 filename (str): filename 75 name (str): contract name 76 77 Returns: 78 str: Combined names 79 """ 80 return filename + ":" + name
Combine the filename with the contract name
Args: filename (str): filename name (str): contract name
Returns: str: Combined names
126def convert_filename( 127 used_filename: Union[str, Path], 128 relative_to_short: Callable[[Path], Path], 129 crytic_compile: "CryticCompile", 130 working_dir: Optional[Union[str, Path]] = None, 131) -> Filename: 132 """Convert a filename to CryticCompile Filename object. 133 The used_filename can be absolute, relative, or missing node_modules/contracts directory 134 135 Args: 136 used_filename (Union[str, Path]): Used filename 137 relative_to_short (Callable[[Path], Path]): Callback to translate the relative to short 138 crytic_compile (CryticCompile): Associated CryticCompile object 139 working_dir (Optional[Union[str, Path]], optional): Working directory. Defaults to None. 140 141 Returns: 142 Filename: Filename converted 143 """ 144 filename_txt = used_filename 145 if platform.system() == "Windows": 146 elements = list(Path(filename_txt).parts) 147 if elements[0] == "/" or elements[0] == "\\": 148 elements = elements[1:] # remove '/' 149 elements[0] = elements[0] + ":/" # add :/ 150 filename = Path(*elements) 151 else: 152 filename = Path(filename_txt) 153 154 # cwd points to the directory to be used 155 if working_dir is None: 156 cwd = Path.cwd() 157 else: 158 working_dir = Path(working_dir) 159 if working_dir.is_absolute(): 160 cwd = working_dir 161 else: 162 cwd = Path.cwd().joinpath(Path(working_dir)).resolve() 163 164 if crytic_compile.package_name: 165 try: 166 filename = filename.relative_to(Path(crytic_compile.package_name)) 167 except ValueError: 168 pass 169 170 filename = _verify_filename_existence(filename, cwd) 171 172 absolute = Path(os.path.abspath(filename)) 173 174 # This returns original path if *path* and *start* are on different drives (for Windows platform). 175 try: 176 relative = Path(os.path.relpath(filename, Path.cwd())) 177 except ValueError: 178 relative = Path(filename) 179 180 # Build the short path 181 try: 182 if cwd.is_absolute(): 183 short = absolute.relative_to(cwd) 184 else: 185 short = relative.relative_to(cwd) 186 except ValueError: 187 short = relative 188 except RuntimeError: 189 short = relative 190 191 short = relative_to_short(short) 192 # Starting with v0.8.8 (https://github.com/ethereum/solidity/pull/11545), solc normalizes the paths to not include the drive on Windows, 193 # so it's important we use posix path here to avoid issues with the path comparison. 194 return Filename( 195 absolute=absolute.as_posix(), 196 relative=relative.as_posix(), 197 short=short.as_posix(), 198 used=Path(used_filename).as_posix(), 199 )
Convert a filename to CryticCompile Filename object. The used_filename can be absolute, relative, or missing node_modules/contracts directory
Args: used_filename (Union[str, Path]): Used filename relative_to_short (Callable[[Path], Path]): Callback to translate the relative to short crytic_compile (CryticCompile): Associated CryticCompile object working_dir (Optional[Union[str, Path]], optional): Working directory. Defaults to None.
Returns: Filename: Filename converted