diff --git a/solc_select/constants.py b/solc_select/constants.py index 4d696ae..7ba4041 100644 --- a/solc_select/constants.py +++ b/solc_select/constants.py @@ -1,15 +1,12 @@ import os from pathlib import Path -# DIRs path -if "VIRTUAL_ENV" in os.environ: - HOME_DIR = Path(os.environ["VIRTUAL_ENV"]) -else: - HOME_DIR = Path.home() -SOLC_SELECT_DIR = HOME_DIR.joinpath(".solc-select") -ARTIFACTS_DIR = SOLC_SELECT_DIR.joinpath("artifacts") +# Directory paths +HOME_DIR = Path(os.environ.get("VIRTUAL_ENV", Path.home())) +SOLC_SELECT_DIR = HOME_DIR / ".solc-select" +ARTIFACTS_DIR = SOLC_SELECT_DIR / "artifacts" -# CLI Commands +# CLI commands INSTALL_COMMAND = "install" USE_COMMAND = "use" VERSIONS_COMMAND = "versions" diff --git a/solc_select/infrastructure/filesystem.py b/solc_select/infrastructure/filesystem.py index 5508d8a..46fbbed 100644 --- a/solc_select/infrastructure/filesystem.py +++ b/solc_select/infrastructure/filesystem.py @@ -30,20 +30,19 @@ def get_current_version(self) -> SolcVersion | None: return None global_version_file = self.config_dir / "global-version" - if global_version_file.exists(): - try: - with open(global_version_file, encoding="utf-8") as f: - return SolcVersion.parse(f.read().strip()) - except (OSError, ValueError): - return None + if not global_version_file.exists(): + return None - return None + try: + version_text = global_version_file.read_text(encoding="utf-8").strip() + return SolcVersion.parse(version_text) + except (OSError, ValueError): + return None def set_global_version(self, version: SolcVersion) -> None: """Set the global version.""" global_version_file = self.config_dir / "global-version" - with open(global_version_file, "w", encoding="utf-8") as f: - f.write(str(version)) + global_version_file.write_text(str(version), encoding="utf-8") def get_version_source(self) -> str: """Get the source of the current version setting.""" @@ -77,17 +76,17 @@ def get_installed_versions(self) -> list[SolcVersion]: installed = [] for item in self.artifacts_dir.iterdir(): - if item.is_dir() and item.name.startswith("solc-"): - version_str = item.name.replace("solc-", "") - try: - version = SolcVersion.parse(version_str) - if self.is_installed(version): - installed.append(version) - except ValueError: - continue - - installed.sort() - return installed + if not (item.is_dir() and item.name.startswith("solc-")): + continue + version_str = item.name.removeprefix("solc-") + try: + version = SolcVersion.parse(version_str) + if self.is_installed(version): + installed.append(version) + except ValueError: + pass + + return sorted(installed) def is_installed(self, version: SolcVersion) -> bool: """Check if a version is installed.""" diff --git a/solc_select/models/artifacts.py b/solc_select/models/artifacts.py index ecbfdd7..c51b48b 100644 --- a/solc_select/models/artifacts.py +++ b/solc_select/models/artifacts.py @@ -33,11 +33,9 @@ def __post_init__(self) -> None: raise ValueError("Download URL cannot be empty") if not self.checksum_sha256: raise ValueError("SHA256 checksum cannot be empty") - # Normalize by removing 0x prefix - if self.checksum_sha256.startswith("0x"): - object.__setattr__(self, "checksum_sha256", self.checksum_sha256[2:]) - if self.checksum_keccak256 and self.checksum_keccak256.startswith("0x"): - object.__setattr__(self, "checksum_keccak256", self.checksum_keccak256[2:]) + self.checksum_sha256 = self.checksum_sha256.removeprefix("0x") + if self.checksum_keccak256: + self.checksum_keccak256 = self.checksum_keccak256.removeprefix("0x") @property def is_zip_archive(self) -> bool: diff --git a/solc_select/models/platforms.py b/solc_select/models/platforms.py index 4d4bac1..631c23e 100644 --- a/solc_select/models/platforms.py +++ b/solc_select/models/platforms.py @@ -25,18 +25,22 @@ def __post_init__(self) -> None: def get_capability(self) -> PlatformCapability: """Get the capability declaration for this platform.""" - key = f"{self.os_type}-{self.architecture}" - return CAPABILITY_REGISTRY.get(key, self._create_default_capability()) + identifier = self.to_identifier() + return CAPABILITY_REGISTRY.get(str(identifier), self._create_default_capability()) def _create_default_capability(self) -> PlatformCapability: """Create default capability (native-only, no emulation).""" - platform_id = PlatformIdentifier(self.os_type, self.architecture) + platform_id = self.to_identifier() return PlatformCapability( host_platform=platform_id, native_support=platform_id, emulation_capabilities=[], ) + def to_identifier(self) -> PlatformIdentifier: + """Convert to a PlatformIdentifier.""" + return PlatformIdentifier(self.os_type, self.architecture) + @classmethod def current(cls) -> "Platform": """Get the current system platform.""" diff --git a/solc_select/platform_capabilities.py b/solc_select/platform_capabilities.py index e01c5cd..3dedb03 100644 --- a/solc_select/platform_capabilities.py +++ b/solc_select/platform_capabilities.py @@ -12,6 +12,9 @@ class PlatformIdentifier: os_type: str # 'linux', 'darwin', 'windows' architecture: str # 'amd64', 'arm64' + def __str__(self) -> str: + return f"{self.os_type}-{self.architecture}" + @dataclass(frozen=True) class EmulationCapability: diff --git a/solc_select/repositories.py b/solc_select/repositories.py index 99577b0..430095a 100644 --- a/solc_select/repositories.py +++ b/solc_select/repositories.py @@ -26,18 +26,10 @@ def __init__( has_latest_release: bool = False, ): self.session = session - self._base_url = base_url - self._list_url = list_url + self.base_url = base_url + self.list_url = list_url self._has_latest_release = has_latest_release - @property - def base_url(self) -> str: - return self._base_url - - @property - def list_url(self) -> str: - return self._list_url - @lru_cache(maxsize=5) # noqa: B019 def _fetch_list_json(self) -> dict[str, Any]: response = self.session.get(self.list_url) @@ -77,14 +69,10 @@ def get_checksums(self, version: SolcVersion) -> tuple[str, str | None]: if not matches or not matches[0]["sha256"]: raise ValueError(f"Unable to retrieve checksum for {version}") - sha256_hash = matches[0]["sha256"] + sha256_hash = matches[0]["sha256"].removeprefix("0x") keccak256_hash = matches[0].get("keccak256") - - # Normalize checksums by removing 0x prefix if present - if sha256_hash and sha256_hash.startswith("0x"): - sha256_hash = sha256_hash[2:] - if keccak256_hash and keccak256_hash.startswith("0x"): - keccak256_hash = keccak256_hash[2:] + if keccak256_hash: + keccak256_hash = keccak256_hash.removeprefix("0x") return sha256_hash, keccak256_hash diff --git a/solc_select/services/artifact_manager.py b/solc_select/services/artifact_manager.py index fa59086..8e06ad2 100644 --- a/solc_select/services/artifact_manager.py +++ b/solc_select/services/artifact_manager.py @@ -95,6 +95,23 @@ def verify_checksum(self, artifact: SolcArtifact, file_handle: BufferedRandom) - if artifact.checksum_keccak256 and artifact.checksum_keccak256 != local_keccak256: raise ChecksumMismatchError(artifact.checksum_keccak256, local_keccak256, "Keccak256") + def _download_artifact(self, artifact: SolcArtifact) -> None: + """Download artifact and verify checksums.""" + response = self.session.get(artifact.download_url, stream=True) + response.raise_for_status() + + with open(artifact.file_path, "w+b", opener=partial(os.open, mode=0o664)) as f: + try: + for chunk in response.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + except KeyboardInterrupt: + if artifact.file_path.exists(): + artifact.file_path.unlink(missing_ok=True) + raise + + self.verify_checksum(artifact, f) + def download_and_install(self, version: SolcVersion, silent: bool = False) -> bool: """Download and install a Solidity compiler version.""" if self.filesystem.is_installed(version): @@ -115,21 +132,7 @@ def download_and_install(self, version: SolcVersion, silent: bool = False) -> bo self.filesystem.ensure_artifact_directory(version) try: - response = self.session.get(artifact.download_url, stream=True) - response.raise_for_status() - - with open(artifact.file_path, "w+b", opener=partial(os.open, mode=0o664)) as f: - try: - for chunk in response.iter_content(chunk_size=8192): - if chunk: - f.write(chunk) - except KeyboardInterrupt: - # Clean up partially downloaded file on interrupt - if artifact.file_path.exists(): - artifact.file_path.unlink(missing_ok=True) - raise - - self.verify_checksum(artifact, f) + self._download_artifact(artifact) if artifact.is_zip_archive: self._extract_zip_archive(artifact) diff --git a/solc_select/services/platform_service.py b/solc_select/services/platform_service.py index 83e4580..eeaacc8 100644 --- a/solc_select/services/platform_service.py +++ b/solc_select/services/platform_service.py @@ -34,7 +34,7 @@ def warn_about_arm64_compatibility(self, force: bool = False) -> None: if self.platform.architecture != "arm64": return - warning_file = SOLC_SELECT_DIR.joinpath(".arm64_warning_shown") + warning_file = SOLC_SELECT_DIR / ".arm64_warning_shown" if not force and warning_file.exists(): return diff --git a/solc_select/services/repository_matcher.py b/solc_select/services/repository_matcher.py index 06e9665..44fcfd9 100644 --- a/solc_select/services/repository_matcher.py +++ b/solc_select/services/repository_matcher.py @@ -77,7 +77,7 @@ def find_repository_for_version( if not exact or str(version) in repo.available_versions: return repo, target_platform - platform_list = ", ".join(str(p) for p in runnable_platforms) + platform_list = ", ".join(map(str, runnable_platforms)) raise VersionNotFoundError( str(version), available_versions=[], @@ -124,15 +124,13 @@ def _create_repository( repository_id = manifest.repository_id if repository_id == "soliditylang": - platform_obj = Platform(os_type=platform.os_type, architecture=platform.architecture) + platform_obj = Platform(platform.os_type, platform.architecture) return SoliditylangRepository(platform_obj, self.session) - factories = { - "crytic": CryticRepository, - "alloy": AlloyRepository, - } - factory = factories.get(repository_id) - if factory is not None: - return factory(self.session) + if repository_id == "crytic": + return CryticRepository(self.session) + + if repository_id == "alloy": + return AlloyRepository(self.session) raise ValueError(f"Unknown repository: {repository_id}") diff --git a/solc_select/services/solc_service.py b/solc_select/services/solc_service.py index 69e0152..16a9fb5 100644 --- a/solc_select/services/solc_service.py +++ b/solc_select/services/solc_service.py @@ -48,7 +48,7 @@ def __init__(self, platform: Platform | None = None): ) self.platform_service = PlatformService(platform) - def get_current_version(self) -> tuple[SolcVersion | None, str]: + def get_current_version(self) -> tuple[SolcVersion, str]: """Get the current version and its source.""" version = self.filesystem.get_current_version() source = self.filesystem.get_version_source() @@ -57,8 +57,7 @@ def get_current_version(self) -> tuple[SolcVersion | None, str]: raise NoVersionSetError() if not self.filesystem.is_installed(version): - installed_versions = self.filesystem.get_installed_versions() - installed_strs = [str(v) for v in installed_versions] + installed_strs = [str(v) for v in self.filesystem.get_installed_versions()] raise VersionNotInstalledError(str(version), installed_strs, source) return version, source @@ -153,9 +152,6 @@ def execute_solc(self, args: list[str]) -> None: print(f"Error: {e}", file=sys.stderr) sys.exit(1) - if version is None: - sys.exit(1) - binary_path = self.filesystem.get_binary_path(version) artifact = self.artifact_manager.create_local_artifact_metadata(version) diff --git a/solc_select/services/version_manager.py b/solc_select/services/version_manager.py index 3cb3072..2267306 100644 --- a/solc_select/services/version_manager.py +++ b/solc_select/services/version_manager.py @@ -76,14 +76,10 @@ def resolve_version_strings(self, version_strings: list[str]) -> list[SolcVersio if "all" in version_strings: return self.get_available_versions() - versions = [] - for version_str in version_strings: - if version_str == "latest": - versions.append(self.get_latest_version()) - else: - versions.append(self.validate_version(version_str)) - - return versions + return [ + self.get_latest_version() if v == "latest" else self.validate_version(v) + for v in version_strings + ] def get_installable_versions(self, installed_versions: list[SolcVersion]) -> list[SolcVersion]: """Get versions that can be installed (not already installed)."""