1
1
"""This module is an all-in-one parser and validator for Git URLs.
2
2
3
3
- Detection: :meth:`GitURL.is_valid()`
4
- - Parse: :class:`GitURL`
4
+ - Parse:
5
5
6
6
compare to :class:`urllib.parse.ParseResult`
7
7
8
- - Output ``git(1)`` URL: :meth:`GitURL.to_url()`
8
+ - Compatibility focused: :class:`GitURL`: Will work with ``git(1)`` as well as
9
+ ``pip(1)`` style URLs
10
+
11
+ - Output ``git(1)`` URL: :meth:`GitURL.to_url()`
12
+ - Strict ``git(1)`` compatibility: :class:`GitBaseURL`.
13
+
14
+ - Output ``git(1)`` URL: :meth:`GitBaseURL.to_url()`
9
15
- Extendable via :class:`~libvcs.parse.base.MatcherRegistry`,
10
16
:class:`~libvcs.parse.base.Matcher`
11
17
"""
23
29
# We modified it to have groupings
24
30
SCP_REGEX = r"""
25
31
# Optional user, e.g. 'git@'
26
- (?P<user>( \w+))?@
32
+ (( ?P<user>\w+)@)?
27
33
# Server, e.g. 'github.com'.
28
34
(?P<hostname>([^/:]+)):
29
35
# The server-side path. e.g. 'user/project.git'. Must start with an
33
39
"""
34
40
35
41
RE_PATH = r"""
42
+ ((?P<user>\w+)@)?
36
43
(?P<hostname>([^/:]+))
44
+ (:(?P<port>\d{1,5}))?
37
45
(?P<separator>[:,/])?
38
46
(?P<path>
39
- (\w[^:.]*) # cut the path at . to negate .git
47
+ (\w[^:.@ ]*) # cut the path at . to negate .git, @ from pip
40
48
)?
41
49
"""
42
50
100
108
)
101
109
"""
102
110
103
- RE_PIP_SCHEME_WITH_HTTP = r"""
111
+ RE_PIP_SCP_SCHEME = r"""
104
112
(?P<scheme>
105
113
(
106
114
git\+ssh|
107
- git\+https|
108
- git\+http|
109
115
git\+file
110
116
)
111
117
)
112
118
"""
113
119
120
+ RE_PIP_REV = r"""
121
+ (@(?P<rev>.*))
122
+ """
123
+
124
+
114
125
PIP_DEFAULT_MATCHERS : list [Matcher ] = [
115
126
Matcher (
116
127
label = "pip-url" ,
117
128
description = "pip-style git URL" ,
118
129
pattern = re .compile (
119
130
rf"""
120
- { RE_PIP_SCHEME_WITH_HTTP }
131
+ { RE_PIP_SCHEME }
121
132
://
122
133
{ RE_PATH }
123
134
{ RE_SUFFIX } ?
135
+ { RE_PIP_REV } ?
124
136
""" ,
125
137
re .VERBOSE ,
126
138
),
130
142
description = "pip-style git ssh/scp URL" ,
131
143
pattern = re .compile (
132
144
rf"""
133
- { RE_PIP_SCHEME }
145
+ { RE_PIP_SCP_SCHEME }
134
146
{ SCP_REGEX } ?
135
- { RE_SUFFIX }
147
+ { RE_SUFFIX } ?
148
+ { RE_PIP_REV } ?
136
149
""" ,
137
150
re .VERBOSE ,
138
151
),
142
155
label = "pip-file-url" ,
143
156
description = "pip-style git+file:// URL" ,
144
157
pattern = re .compile (
145
- r """
158
+ rf """
146
159
(?P<scheme>git\+file)://
147
- (?P<path>.*)
160
+ (?P<path>[^@]*)
161
+ { RE_PIP_REV } ?
148
162
""" ,
149
163
re .VERBOSE ,
150
164
),
193
207
194
208
195
209
@dataclasses .dataclass (repr = False )
196
- class GitURL (URLProtocol , SkipDefaultFieldsReprMixin ):
210
+ class GitBaseURL (URLProtocol , SkipDefaultFieldsReprMixin ):
197
211
"""Git gepository location. Parses URLs on initialization.
198
212
199
213
Examples
@@ -216,9 +230,9 @@ class GitURL(URLProtocol, SkipDefaultFieldsReprMixin):
216
230
217
231
>>> GitURL(url='git@github.com:vcs-python/libvcs.git')
218
232
GitURL(url=git@github.com:vcs-python/libvcs.git,
233
+ user=git,
219
234
hostname=github.com,
220
235
path=vcs-python/libvcs,
221
- user=git,
222
236
suffix=.git,
223
237
matcher=core-git-scp)
224
238
@@ -229,28 +243,18 @@ class GitURL(URLProtocol, SkipDefaultFieldsReprMixin):
229
243
----------
230
244
matcher : str
231
245
name of the :class:`~libvcs.parse.base.Matcher`
232
-
233
- branch : str, optional
234
- Default URL parsers don't output these,
235
- can be added by extending or passing manually
236
246
"""
237
247
238
248
url : str
239
249
scheme : Optional [str ] = None
250
+ user : Optional [str ] = None
240
251
hostname : Optional [str ] = None
252
+ port : Optional [int ] = None
241
253
path : Optional [str ] = None
242
- user : Optional [str ] = None
243
254
244
255
# Decoration
245
256
suffix : Optional [str ] = None
246
257
247
- #
248
- # commit-ish: tag, branch, ref, revision
249
- #
250
- ref : Optional [str ] = None
251
- branch : Optional [str ] = None
252
- tag : Optional [str ] = None
253
-
254
258
matcher : Optional [str ] = None
255
259
matchers = MatcherRegistry = MatcherRegistry (
256
260
_matchers = {m .label : m for m in DEFAULT_MATCHERS }
@@ -298,9 +302,9 @@ def to_url(self) -> str:
298
302
299
303
>>> git_location
300
304
GitURL(url=git@github.com:vcs-python/libvcs.git,
305
+ user=git,
301
306
hostname=github.com,
302
307
path=vcs-python/libvcs,
303
- user=git,
304
308
suffix=.git,
305
309
matcher=core-git-scp)
306
310
@@ -333,3 +337,87 @@ def to_url(self) -> str:
333
337
parts .append (self .suffix )
334
338
335
339
return "" .join (part for part in parts if isinstance (part , str ))
340
+
341
+
342
+ @dataclasses .dataclass (repr = False )
343
+ class GitPipURL (GitBaseURL , URLProtocol , SkipDefaultFieldsReprMixin ):
344
+ """Supports pip git URLs."""
345
+
346
+ # commit-ish (rev): tag, branch, ref
347
+ rev : Optional [str ] = None
348
+
349
+ matchers = MatcherRegistry = MatcherRegistry (
350
+ _matchers = {m .label : m for m in PIP_DEFAULT_MATCHERS }
351
+ )
352
+
353
+ def to_url (self ) -> str :
354
+ """Exports a pip-compliant URL.
355
+
356
+ Examples
357
+ --------
358
+
359
+ >>> git_location = GitPipURL(
360
+ ... url='git+ssh://git@bitbucket.example.com:7999/PROJ/repo.git'
361
+ ... )
362
+
363
+ >>> git_location
364
+ GitPipURL(url=git+ssh://git@bitbucket.example.com:7999/PROJ/repo.git,
365
+ scheme=git+ssh,
366
+ user=git,
367
+ hostname=bitbucket.example.com,
368
+ port=7999,
369
+ path=PROJ/repo,
370
+ suffix=.git,
371
+ matcher=pip-url)
372
+
373
+ >>> git_location.path = 'libvcs/vcspull'
374
+
375
+ >>> git_location.to_url()
376
+ 'git+ssh://bitbucket.example.com/libvcs/vcspull.git'
377
+
378
+ It also accepts revisions, e.g. branch, tag, ref:
379
+
380
+ >>> git_location = GitPipURL(
381
+ ... url='git+https://github.com/vcs-python/libvcs.git@v0.10.0'
382
+ ... )
383
+
384
+ >>> git_location
385
+ GitPipURL(url=git+https://github.com/vcs-python/libvcs.git@v0.10.0,
386
+ scheme=git+https,
387
+ hostname=github.com,
388
+ path=vcs-python/libvcs,
389
+ suffix=.git,
390
+ matcher=pip-url,
391
+ rev=v0.10.0)
392
+
393
+ >>> git_location.path = 'libvcs/vcspull'
394
+
395
+ >>> git_location.to_url()
396
+ 'git+https://github.com/libvcs/vcspull.git@v0.10.0'
397
+ """
398
+ url = super ().to_url ()
399
+
400
+ if self .rev :
401
+ url = f"{ url } @{ self .rev } "
402
+
403
+ return url
404
+
405
+
406
+ @dataclasses .dataclass (repr = False )
407
+ class GitURL (GitPipURL , GitBaseURL , URLProtocol , SkipDefaultFieldsReprMixin ):
408
+ """Batteries included URL Parser. Supports git(1) and pip URLs.
409
+
410
+ **Ancestors (MRO)**
411
+ This URL parser inherits methods and attributes from the following parsers:
412
+
413
+ - :class:`GitPipURL`
414
+
415
+ - :meth:`GitPipURL.to_url`
416
+ - :class:`GitBaseURL`
417
+
418
+ - :meth:`GitBaseURL.to_url`
419
+ """
420
+
421
+ matchers = MatcherRegistry = MatcherRegistry (
422
+ _matchers = {m .label : m for m in [* DEFAULT_MATCHERS , * PIP_DEFAULT_MATCHERS ]}
423
+ )
0 commit comments