clan-core/pkgs/clan-cli/clan_cli/clan_uri.py

126 lines
4.3 KiB
Python
Raw Normal View History

2023-12-05 17:08:27 +00:00
# Import the urllib.parse, enum and dataclasses modules
2023-12-05 17:16:51 +00:00
import dataclasses
2023-12-05 15:17:15 +00:00
import urllib.parse
2023-12-05 17:08:27 +00:00
from dataclasses import dataclass
2023-12-05 17:16:51 +00:00
from enum import Enum, member
2023-12-05 17:08:27 +00:00
from pathlib import Path
2023-12-06 16:13:32 +00:00
from typing import Self
2023-12-05 17:16:51 +00:00
2023-12-05 15:17:15 +00:00
from .errors import ClanError
2023-12-05 17:16:51 +00:00
2023-12-05 15:17:15 +00:00
2023-12-05 17:08:27 +00:00
# Define an enum with different members that have different values
2023-12-05 15:17:15 +00:00
class ClanScheme(Enum):
2023-12-05 17:08:27 +00:00
# Use the dataclass decorator to add fields and methods to the members
@member
@dataclass
2023-12-05 17:16:51 +00:00
class HTTP:
url: str # The url field holds the HTTP URL
def __str__(self) -> str:
return f"HTTP({self.url})" # The __str__ method returns a custom string representation
2023-12-05 17:08:27 +00:00
@member
@dataclass
2023-12-05 17:16:51 +00:00
class FILE:
path: Path # The path field holds the local path
def __str__(self) -> str:
return f"FILE({self.path})" # The __str__ method returns a custom string representation
2023-12-05 17:08:27 +00:00
2023-12-05 15:17:15 +00:00
2023-12-05 17:08:27 +00:00
# Parameters defined here will be DELETED from the nested uri
# so make sure there are no conflicts with other webservices
@dataclass
2023-12-05 17:16:51 +00:00
class ClanParameters:
flake_attr: str = "defaultVM"
2023-12-05 15:17:15 +00:00
2023-12-05 17:16:51 +00:00
2023-12-05 15:17:15 +00:00
# Define the ClanURI class
class ClanURI:
# Initialize the class with a clan:// URI
def __init__(self, uri: str) -> None:
2023-12-12 20:31:47 +00:00
self._full_uri = uri
2023-12-05 17:08:27 +00:00
# Check if the URI starts with clan://
2023-12-05 15:17:15 +00:00
if uri.startswith("clan://"):
2023-12-05 17:08:27 +00:00
self._nested_uri = uri[7:]
2023-12-05 15:17:15 +00:00
else:
2023-12-06 16:13:32 +00:00
raise ClanError(f"Invalid scheme: expected clan://, got {uri}")
2023-12-05 15:17:15 +00:00
# Parse the URI into components
2023-12-05 17:08:27 +00:00
# scheme://netloc/path;parameters?query#fragment
self._components = urllib.parse.urlparse(self._nested_uri)
# Parse the query string into a dictionary
query = urllib.parse.parse_qs(self._components.query)
2023-12-05 17:08:27 +00:00
2023-12-12 20:31:47 +00:00
new_params: dict[str, str] = {}
2023-12-05 17:08:27 +00:00
for field in dataclasses.fields(ClanParameters):
if field.name in query:
values = query[field.name]
if len(values) > 1:
2023-12-06 16:13:32 +00:00
raise ClanError(f"Multiple values for parameter: {field.name}")
2023-12-12 20:31:47 +00:00
new_params[field.name] = values[0]
2023-12-05 17:08:27 +00:00
# Remove the field from the query dictionary
# clan uri and nested uri share one namespace for query parameters
# we need to make sure there are no conflicts
del query[field.name]
2023-12-05 17:08:27 +00:00
new_query = urllib.parse.urlencode(query, doseq=True)
self._components = self._components._replace(query=new_query)
2023-12-12 20:31:47 +00:00
self.params = ClanParameters(**new_params)
2023-12-05 17:08:27 +00:00
comb = (
self._components.scheme,
self._components.netloc,
self._components.path,
self._components.params,
self._components.query,
self._components.fragment,
)
match comb:
case ("http" | "https", _, _, _, _, _):
2023-12-05 17:16:51 +00:00
self.scheme = ClanScheme.HTTP.value(self._components.geturl()) # type: ignore
case ("file", "", path, "", "", "") | ("", "", path, "", "", ""): # type: ignore
self.scheme = ClanScheme.FILE.value(Path(path)) # type: ignore
2023-12-05 17:08:27 +00:00
case _:
raise ClanError(f"Unsupported uri components: {comb}")
def get_internal(self) -> str:
match self.scheme:
case ClanScheme.FILE.value(path):
return str(path) # type: ignore
case ClanScheme.HTTP.value(url):
return url # type: ignore
case _:
raise ClanError(f"Unsupported uri components: {self.scheme}")
2023-12-12 20:31:47 +00:00
def get_full_uri(self) -> str:
return self._full_uri
@classmethod
def from_path(cls, path: Path, params: ClanParameters | None = None) -> Self: # noqa
return cls.from_str(str(path), params)
@classmethod
2023-12-12 20:31:47 +00:00
def from_str(cls, url: str, params: ClanParameters | None = None) -> Self: # noqa
prefix = "clan://"
if url.startswith(prefix):
url = url[len(prefix) :]
2023-12-12 20:31:47 +00:00
if params is None:
return cls(f"clan://{url}")
comp = urllib.parse.urlparse(url)
query = urllib.parse.parse_qs(comp.query)
query.update(params.__dict__)
new_query = urllib.parse.urlencode(query, doseq=True)
comp = comp._replace(query=new_query)
new_url = urllib.parse.urlunparse(comp)
return cls(f"clan://{new_url}")
2023-12-08 11:18:55 +00:00
def __str__(self) -> str:
return self.get_full_uri()