Skip to content

Commit 6355aca

Browse files
committed
feat(parsing): Experimental support for VCS URL Parsing
1 parent edb3935 commit 6355aca

5 files changed

Lines changed: 960 additions & 0 deletions

File tree

libvcs/parse/__init__.py

Whitespace-only changes.

libvcs/parse/base.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import dataclasses
2+
from typing import Iterator, Pattern, Protocol
3+
4+
from libvcs._internal.dataclasses import SkipDefaultFieldsReprMixin
5+
6+
7+
class URLProtocol(Protocol):
8+
"""Common interface for VCS URL Parsers."""
9+
10+
def __init__(self, url: str):
11+
...
12+
13+
def to_url(self) -> str:
14+
...
15+
16+
def is_valid(self, url: str) -> bool:
17+
...
18+
19+
20+
@dataclasses.dataclass(repr=False)
21+
class Matcher(SkipDefaultFieldsReprMixin):
22+
"""Structure for a matcher"""
23+
24+
label: str
25+
"""Computer readable name / ID"""
26+
description: str
27+
"""Human readable description"""
28+
pattern: Pattern
29+
"""Regex pattern"""
30+
pattern_defaults: dict = dataclasses.field(default_factory=dict)
31+
32+
33+
@dataclasses.dataclass(repr=False)
34+
class MatcherRegistry(SkipDefaultFieldsReprMixin):
35+
"""Pattern matching and parsing capabilities for URL parsers, e.g. GitURL"""
36+
37+
_matchers: dict[str, Matcher] = dataclasses.field(default_factory=dict)
38+
39+
def register(self, cls: Matcher) -> None:
40+
"""
41+
>>> from libvcs.parse.git import GitURL
42+
43+
>>> GitURL.is_valid(url="git+ssh://git@github.com/tony/AlgoXY.git")
44+
False
45+
46+
>>> class GitHubPrefix(Matcher):
47+
... label = 'gh-prefix'
48+
... description ='Matches prefixes like github:org/repo'
49+
... pattern = r'^github:(?P<path>)'
50+
... pattern_defaults = {
51+
... 'hostname': 'github.com',
52+
... 'scheme': 'https'
53+
... }
54+
55+
>>> class GitHubLocation(GitURL):
56+
... matchers = MatcherRegistry = MatcherRegistry(
57+
... _matchers={'github_prefix': GitHubPrefix}
58+
... )
59+
60+
>>> GitHubLocation.is_valid(url='github:vcs-python/libvcs')
61+
True
62+
63+
>>> GitHubLocation.is_valid(url='gitlab:vcs-python/libvcs')
64+
False
65+
66+
>>> class GitLabPrefix(Matcher):
67+
... label = 'gl-prefix'
68+
... description ='Matches prefixes like gitlab:org/repo'
69+
... pattern = r'^gitlab:(?P<path>)'
70+
... pattern_defaults = {
71+
... 'hostname': 'gitlab.com',
72+
... 'scheme': 'https',
73+
... 'suffix': '.git'
74+
... }
75+
76+
Option 1: Create a brand new matcher
77+
78+
>>> class GitLabLocation(GitURL):
79+
... matchers = MatcherRegistry = MatcherRegistry(
80+
... _matchers={'gitlab_prefix': GitLabPrefix}
81+
... )
82+
83+
>>> GitLabLocation.is_valid(url='gitlab:vcs-python/libvcs')
84+
True
85+
86+
Option 2 (global, everywhere): Add to the global :class:`GitURL`:
87+
88+
>>> GitURL.is_valid(url='gitlab:vcs-python/libvcs')
89+
False
90+
91+
>>> GitURL.matchers.register(GitLabPrefix)
92+
93+
>>> GitURL.is_valid(url='gitlab:vcs-python/libvcs')
94+
True
95+
96+
git URLs + pip-style git URLs:
97+
98+
>>> from libvcs.parse.git import DEFAULT_MATCHERS, PIP_DEFAULT_MATCHERS
99+
100+
>>> class GitURLWithPip(GitURL):
101+
... matchers = MatcherRegistry = MatcherRegistry(
102+
... _matchers={m.label: m for m in [*DEFAULT_MATCHERS, *PIP_DEFAULT_MATCHERS]}
103+
... )
104+
105+
>>> GitURLWithPip.is_valid(url="git+ssh://git@github.com/tony/AlgoXY.git")
106+
True
107+
108+
>>> GitURLWithPip(url="git+ssh://git@github.com/tony/AlgoXY.git")
109+
GitURLWithPip(url=git+ssh://git@github.com/tony/AlgoXY.git,
110+
scheme=git+ssh,
111+
hostname=git@github.com,
112+
path=tony/AlgoXY,
113+
suffix=.git,
114+
matcher=pip-url)
115+
""" # NOQA: E501
116+
if cls.label not in self._matchers:
117+
self._matchers[cls.label] = cls
118+
119+
def unregister(self, label: str) -> None:
120+
if label in self._matchers:
121+
del self._matchers[label]
122+
123+
def __iter__(self) -> Iterator[str]:
124+
return self._matchers.__iter__()
125+
126+
def values(self): # https://github.com/python/typing/discussions/1033
127+
return self._matchers.values()

0 commit comments

Comments
 (0)