clan_cli: Remodeled ClanURI parser

This commit is contained in:
Qubasa
2023-12-05 18:08:27 +01:00
parent cebc52ba87
commit 34a943b3ad
2 changed files with 109 additions and 45 deletions

View File

@@ -1,50 +1,93 @@
# Import the urllib.parse module # Import the urllib.parse, enum and dataclasses modules
import urllib.parse import urllib.parse
from enum import Enum from enum import Enum, member
from dataclasses import dataclass
import dataclasses
from pathlib import Path
from .errors import ClanError from .errors import ClanError
from typing import List
# Define an enum with different members that have different values
class ClanScheme(Enum): class ClanScheme(Enum):
HTTP = "http" # Use the dataclass decorator to add fields and methods to the members
HTTPS = "https" @member
FILE = "file" @dataclass
class HTTP():
url: str # The url field holds the HTTP URL
def __str__(self):
return f"HTTP({self.url})" # The __str__ method returns a custom string representation
@member
@dataclass
class HTTPS():
url: str # The url field holds the HTTPS URL
def __str__(self):
return f"HTTPS({self.url})" # The __str__ method returns a custom string representation
@member
@dataclass
class FILE():
path: Path # The path field holds the local path
def __str__(self):
return f"FILE({self.path})" # The __str__ method returns a custom string representation
# Parameters defined here will be DELETED from the nested uri
# so make sure there are no conflicts with other webservices
@dataclass
class ClanParameters():
flake_attr: str | None
machine: str | None
# Define the ClanURI class # Define the ClanURI class
class ClanURI: class ClanURI:
# Initialize the class with a clan:// URI # Initialize the class with a clan:// URI
def __init__(self, uri: str) -> None: def __init__(self, uri: str) -> None:
# Check if the URI starts with clan://
if uri.startswith("clan://"): if uri.startswith("clan://"):
uri = uri[7:] self._nested_uri = uri[7:]
else: else:
raise ClanError("Invalid scheme: expected clan://, got {}".format(uri)) raise ClanError("Invalid scheme: expected clan://, got {}".format(uri))
# Parse the URI into components # Parse the URI into components
self.components = urllib.parse.urlparse(uri) # scheme://netloc/path;parameters?query#fragment
self._components = urllib.parse.urlparse(self._nested_uri)
try: # Parse the query string into a dictionary
self.scheme = ClanScheme(self.components.scheme) self._query = urllib.parse.parse_qs(self._components.query)
except ValueError:
raise ClanError("Unsupported scheme: {}".format(self.components.scheme))
# Define a method to get the path of the URI params = {}
@property for field in dataclasses.fields(ClanParameters):
def path(self) -> str: if field.name in self._query:
return self.components.path # Check if the field type is a list
if issubclass(field.type, list):
setattr(params, field.name, self._query[field.name])
# Check if the field type is a single value
else:
values = self._query[field.name]
if len(values) > 1:
raise ClanError("Multiple values for parameter: {}".format(field.name))
setattr(params, field.name, values[0])
# 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 self._query[field.name]
else:
params[field.name] = None
self.params = ClanParameters(**params)
# Use the match statement to check the scheme and create a ClanScheme member with the value
match self._components.scheme:
case "http":
self.scheme = ClanScheme.HTTP.value(self._components.geturl())
case "https":
self.scheme = ClanScheme.HTTPS.value(self._components.geturl())
case "file":
self.scheme = ClanScheme.FILE.value(Path(self._components.path))
case _:
raise ClanError("Unsupported scheme: {}".format(self._components.scheme))
@property
def url(self) -> str:
return self.components.geturl()
# Define a method to check if the URI is a remote HTTP URL
def is_remote(self) -> bool:
match self.scheme:
case ClanScheme.HTTP | ClanScheme.HTTPS:
return True
case ClanScheme.FILE:
return False
# Define a method to check if the URI is a local path
def is_local(self) -> bool:
return not self.is_remote()

View File

@@ -1,19 +1,17 @@
import pytest import pytest
from clan_cli.clan_uri import ClanURI from clan_cli.clan_uri import ClanURI, ClanScheme
from clan_cli.errors import ClanError from clan_cli.errors import ClanError
from pathlib import Path
def test_local_uri() -> None: def test_local_uri() -> None:
# Create a ClanURI object from a local URI # Create a ClanURI object from a local URI
uri = ClanURI("clan://file:///home/user/Downloads") uri = ClanURI("clan://file:///home/user/Downloads")
# Check that the URI is local match uri.scheme:
assert uri.is_local() case ClanScheme.FILE.value(path):
# Check that the URI is not remote assert path == Path("/home/user/Downloads") # type: ignore
assert not uri.is_remote() case _:
# Check that the URI path is correct assert False
assert uri.path == "/home/user/Downloads"
def test_unsupported_schema() -> None: def test_unsupported_schema() -> None:
with pytest.raises(ClanError, match="Unsupported scheme: ftp"): with pytest.raises(ClanError, match="Unsupported scheme: ftp"):
@@ -24,11 +22,34 @@ def test_unsupported_schema() -> None:
def test_is_remote() -> None: def test_is_remote() -> None:
# Create a ClanURI object from a remote URI # Create a ClanURI object from a remote URI
uri = ClanURI("clan://https://example.com") uri = ClanURI("clan://https://example.com")
# Check that the URI is remote
assert uri.is_remote()
# Check that the URI is not local
assert not uri.is_local()
# Check that the URI path is correct
assert uri.path == ""
assert uri.url == "https://example.com" match uri.scheme:
case ClanScheme.HTTPS.value(url):
assert url == "https://example.com" # type: ignore
case _:
assert False
def remote_with_clanparams() -> None:
# Create a ClanURI object from a remote URI with parameters
uri = ClanURI("clan://https://example.com?flake_attr=defaultVM")
assert uri.params.flake_attr == "defaultVM"
match uri.scheme:
case ClanScheme.HTTPS.value(url):
assert url == "https://example.com" # type: ignore
case _:
assert False
def remote_with_all_params() -> None:
# Create a ClanURI object from a remote URI with parameters
uri = ClanURI("clan://https://example.com?flake_attr=defaultVM&machine=vm1&password=1234")
assert uri.params.flake_attr == "defaultVM"
assert uri.params.machine == "vm1"
match uri.scheme:
case ClanScheme.HTTPS.value(url):
assert url == "https://example.com&password=1234" # type: ignore
case _:
assert False