urlpattern 0.1.10__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ {
2
+ "python.defaultInterpreterPath": "${workspaceFolder}/.venv",
3
+ "python.testing.pytestArgs": [
4
+ "tests"
5
+ ],
6
+ "python.testing.unittestEnabled": false,
7
+ "python.testing.pytestEnabled": true,
8
+ "[python]": {
9
+ "editor.formatOnSave": true,
10
+ "editor.defaultFormatter": "charliermarsh.ruff"
11
+ }
12
+ }
@@ -141,9 +141,9 @@ dependencies = [
141
141
 
142
142
  [[package]]
143
143
  name = "libc"
144
- version = "0.2.182"
144
+ version = "0.2.183"
145
145
  source = "registry+https://github.com/rust-lang/crates.io-index"
146
- checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
146
+ checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
147
147
 
148
148
  [[package]]
149
149
  name = "litemap"
@@ -159,9 +159,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
159
159
 
160
160
  [[package]]
161
161
  name = "once_cell"
162
- version = "1.21.3"
162
+ version = "1.21.4"
163
163
  source = "registry+https://github.com/rust-lang/crates.io-index"
164
- checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
164
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
165
165
 
166
166
  [[package]]
167
167
  name = "percent-encoding"
@@ -251,19 +251,11 @@ dependencies = [
251
251
  "syn",
252
252
  ]
253
253
 
254
- [[package]]
255
- name = "python-urlpattern"
256
- version = "0.1.10"
257
- dependencies = [
258
- "pyo3",
259
- "urlpattern",
260
- ]
261
-
262
254
  [[package]]
263
255
  name = "quote"
264
- version = "1.0.44"
256
+ version = "1.0.45"
265
257
  source = "registry+https://github.com/rust-lang/crates.io-index"
266
- checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
258
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
267
259
  dependencies = [
268
260
  "proc-macro2",
269
261
  ]
@@ -293,9 +285,9 @@ dependencies = [
293
285
 
294
286
  [[package]]
295
287
  name = "regex-syntax"
296
- version = "0.8.9"
288
+ version = "0.8.10"
297
289
  source = "registry+https://github.com/rust-lang/crates.io-index"
298
- checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
290
+ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
299
291
 
300
292
  [[package]]
301
293
  name = "serde"
@@ -395,6 +387,16 @@ dependencies = [
395
387
  "serde",
396
388
  ]
397
389
 
390
+ [[package]]
391
+ name = "urlpattern"
392
+ version = "0.3.0"
393
+ dependencies = [
394
+ "pyo3",
395
+ "regex",
396
+ "url",
397
+ "urlpattern 0.6.0",
398
+ ]
399
+
398
400
  [[package]]
399
401
  name = "urlpattern"
400
402
  version = "0.6.0"
@@ -1,6 +1,6 @@
1
1
  [package]
2
- name = "python-urlpattern"
3
- version = "0.1.10"
2
+ name = "urlpattern"
3
+ version = "0.3.0"
4
4
  authors = ["방성범 (Bang Seongbeom) <bangseongbeom@gmail.com>"]
5
5
  edition = "2024"
6
6
  description = "An implementation of the URL Pattern Standard for Python written in Rust."
@@ -15,5 +15,7 @@ readme = "README.md"
15
15
  crate-type = ["cdylib"]
16
16
 
17
17
  [dependencies]
18
+ deno-urlpattern = { version = "0.6.0", package = "urlpattern" }
18
19
  pyo3 = "0.28.2"
19
- deno_urlpattern = { package = "urlpattern", version = "0.6.0" }
20
+ regex = "1.12.3"
21
+ url = "2.5.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urlpattern
3
- Version: 0.1.10
3
+ Version: 0.3.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -30,7 +30,7 @@ Project-URL: Homepage, https://github.com/urlpattern/python-urlpattern
30
30
  Project-URL: Issues, https://github.com/urlpattern/python-urlpattern/issues
31
31
  Project-URL: Repository, https://github.com/urlpattern/python-urlpattern.git
32
32
 
33
- # URL Pattern
33
+ # urlpattern
34
34
 
35
35
  [![PyPI - Version](https://img.shields.io/pypi/v/urlpattern)](https://pypi.org/project/urlpattern/)
36
36
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/urlpattern)](https://pypi.org/project/urlpattern/)
@@ -158,3 +158,9 @@ Due to limitations in the dependency [denoland/rust-urlpattern](https://github.c
158
158
 
159
159
  Check `pytest.skip` in [`tests/test_lib.py`](https://github.com/urlpattern/python-urlpattern/blob/main/tests/test_lib.py).
160
160
 
161
+ ## Why camelCase?
162
+
163
+ In this library, some names such as `baseURL` and `hasRegExpGroups` do not use snake_case.
164
+
165
+ Like [`xml.dom`](https://docs.python.org/3/library/xml.dom.html), Python wrappers around web standards typically preserve the original camelCase rather than converting names to snake_case. This library follows that convention as well.
166
+
@@ -1,4 +1,4 @@
1
- # URL Pattern
1
+ # urlpattern
2
2
 
3
3
  [![PyPI - Version](https://img.shields.io/pypi/v/urlpattern)](https://pypi.org/project/urlpattern/)
4
4
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/urlpattern)](https://pypi.org/project/urlpattern/)
@@ -125,3 +125,9 @@ with make_server("", 8000, app) as httpd:
125
125
  Due to limitations in the dependency [denoland/rust-urlpattern](https://github.com/denoland/rust-urlpattern), it may not support all features specified in [the standard](https://urlpattern.spec.whatwg.org/).
126
126
 
127
127
  Check `pytest.skip` in [`tests/test_lib.py`](https://github.com/urlpattern/python-urlpattern/blob/main/tests/test_lib.py).
128
+
129
+ ## Why camelCase?
130
+
131
+ In this library, some names such as `baseURL` and `hasRegExpGroups` do not use snake_case.
132
+
133
+ Like [`xml.dom`](https://docs.python.org/3/library/xml.dom.html), Python wrappers around web standards typically preserve the original camelCase rather than converting names to snake_case. This library follows that convention as well.
@@ -0,0 +1,479 @@
1
+ #![allow(non_snake_case)]
2
+
3
+ use pyo3::{
4
+ BoundObject,
5
+ exceptions::{PyTypeError, PyValueError},
6
+ prelude::*,
7
+ types::{PyDict, PyList, PyString},
8
+ };
9
+ use std::collections::HashMap;
10
+
11
+ #[pyclass(name = "URLPattern")]
12
+ struct UrlPattern(deno_urlpattern::UrlPattern);
13
+
14
+ #[pymethods]
15
+ impl UrlPattern {
16
+ #[new]
17
+ #[pyo3(signature = (input=None, baseURL=None, options=None))]
18
+ fn new(
19
+ input: Option<UrlPatternInput>,
20
+ baseURL: Option<&Bound<'_, PyAny>>,
21
+ options: Option<&Bound<'_, PyDict>>,
22
+ ) -> PyResult<Self> {
23
+ let (base_url, options) = match baseURL {
24
+ Some(value) => {
25
+ if let Ok(options_dict) = value.cast::<PyDict>() {
26
+ (None, Some(options_dict))
27
+ } else if value.is_none() {
28
+ (None, options)
29
+ } else {
30
+ (
31
+ Some(
32
+ value
33
+ .extract::<String>()?
34
+ .parse::<url::Url>()
35
+ .map_err(deno_urlpattern::Error::Url)
36
+ .map_err(Error)?,
37
+ ),
38
+ options,
39
+ )
40
+ }
41
+ }
42
+ None => (None, options),
43
+ };
44
+
45
+ if let Some(UrlPatternInput::Init(_)) = input {
46
+ if let Some(_) = base_url {
47
+ return Err(PyTypeError::new_err("cannot use dict input with baseURL"));
48
+ }
49
+ }
50
+
51
+ let init: deno_urlpattern::UrlPatternInit = match input {
52
+ Some(input) => match input {
53
+ UrlPatternInput::String(input) => {
54
+ deno_urlpattern::UrlPatternInit::parse_constructor_string::<regex::Regex>(
55
+ input.as_str(),
56
+ base_url,
57
+ )
58
+ .map_err(Error)?
59
+ }
60
+ UrlPatternInput::Init(init) => deno_urlpattern::UrlPatternInit {
61
+ protocol: init
62
+ .get_item("protocol")?
63
+ .map(|v| v.extract::<String>())
64
+ .transpose()?,
65
+ username: init
66
+ .get_item("username")?
67
+ .map(|v| v.extract::<String>())
68
+ .transpose()?,
69
+ password: init
70
+ .get_item("password")?
71
+ .map(|v| v.extract::<String>())
72
+ .transpose()?,
73
+ hostname: init
74
+ .get_item("hostname")?
75
+ .map(|v| v.extract::<String>())
76
+ .transpose()?,
77
+ port: init
78
+ .get_item("port")?
79
+ .map(|v| v.extract::<String>())
80
+ .transpose()?,
81
+ pathname: init
82
+ .get_item("pathname")?
83
+ .map(|v| v.extract::<String>())
84
+ .transpose()?,
85
+ search: init
86
+ .get_item("search")?
87
+ .map(|v| v.extract::<String>())
88
+ .transpose()?,
89
+ hash: init
90
+ .get_item("hash")?
91
+ .map(|v| v.extract::<String>())
92
+ .transpose()?,
93
+ base_url: init
94
+ .get_item("baseURL")?
95
+ .map(|v| v.extract::<String>())
96
+ .transpose()?
97
+ .map(|v| v.parse::<url::Url>())
98
+ .transpose()
99
+ .map_err(deno_urlpattern::Error::Url)
100
+ .map_err(Error)?,
101
+ },
102
+ },
103
+ None => deno_urlpattern::UrlPatternInit::default(),
104
+ };
105
+ let options = if let Some(options) = options {
106
+ deno_urlpattern::UrlPatternOptions {
107
+ ignore_case: options
108
+ .get_item("ignoreCase")?
109
+ .map(|v| v.extract::<bool>())
110
+ .transpose()?
111
+ .unwrap_or(false),
112
+ ..deno_urlpattern::UrlPatternOptions::default()
113
+ }
114
+ } else {
115
+ deno_urlpattern::UrlPatternOptions::default()
116
+ };
117
+ Ok(UrlPattern(
118
+ deno_urlpattern::UrlPattern::parse(init, options).map_err(Error)?,
119
+ ))
120
+ }
121
+
122
+ fn __repr__(&self, py: Python) -> String {
123
+ let dict = PyDict::new(py);
124
+ dict.set_item("protocol", self.0.protocol()).unwrap();
125
+ dict.set_item("username", self.0.username()).unwrap();
126
+ dict.set_item("password", self.0.password()).unwrap();
127
+ dict.set_item("hostname", self.0.hostname()).unwrap();
128
+ dict.set_item("port", self.0.port()).unwrap();
129
+ dict.set_item("pathname", self.0.pathname()).unwrap();
130
+ dict.set_item("search", self.0.search()).unwrap();
131
+ dict.set_item("hash", self.0.hash()).unwrap();
132
+ dict.set_item("hasRegExpGroups", self.0.has_regexp_groups())
133
+ .unwrap();
134
+ format!("URLPattern({})", dict)
135
+ }
136
+
137
+ #[pyo3(signature = (input=None, baseURL=None))]
138
+ fn test(&self, input: Option<UrlPatternInput>, baseURL: Option<&str>) -> PyResult<bool> {
139
+ let input: deno_urlpattern::UrlPatternMatchInput = match input {
140
+ Some(input) => match input {
141
+ UrlPatternInput::String(input) => match baseURL {
142
+ Some(base_url) => {
143
+ let base_url = match url::Url::parse(base_url) {
144
+ Ok(url) => url,
145
+ Err(_) => return Ok(false),
146
+ };
147
+ deno_urlpattern::UrlPatternMatchInput::Url(
148
+ match url::Url::options()
149
+ .base_url(Some(&base_url))
150
+ .parse(input.as_ref())
151
+ {
152
+ Ok(url) => url,
153
+ Err(_) => return Ok(false),
154
+ },
155
+ )
156
+ }
157
+ None => deno_urlpattern::UrlPatternMatchInput::Url(
158
+ match input.parse::<url::Url>() {
159
+ Ok(url) => url,
160
+ Err(_) => return Ok(false),
161
+ },
162
+ ),
163
+ },
164
+ UrlPatternInput::Init(init) => {
165
+ if let Some(_) = baseURL {
166
+ return Err(PyTypeError::new_err("cannot use dict input with baseURL"));
167
+ }
168
+
169
+ deno_urlpattern::UrlPatternMatchInput::Init(deno_urlpattern::UrlPatternInit {
170
+ protocol: init
171
+ .get_item("protocol")?
172
+ .map(|v| v.extract::<String>())
173
+ .transpose()?,
174
+ username: init
175
+ .get_item("username")?
176
+ .map(|v| v.extract::<String>())
177
+ .transpose()?,
178
+ password: init
179
+ .get_item("password")?
180
+ .map(|v| v.extract::<String>())
181
+ .transpose()?,
182
+ hostname: init
183
+ .get_item("hostname")?
184
+ .map(|v| v.extract::<String>())
185
+ .transpose()?,
186
+ port: init
187
+ .get_item("port")?
188
+ .map(|v| v.extract::<String>())
189
+ .transpose()?,
190
+ pathname: init
191
+ .get_item("pathname")?
192
+ .map(|v| v.extract::<String>())
193
+ .transpose()?,
194
+ search: init
195
+ .get_item("search")?
196
+ .map(|v| v.extract::<String>())
197
+ .transpose()?,
198
+ hash: init
199
+ .get_item("hash")?
200
+ .map(|v| v.extract::<String>())
201
+ .transpose()?,
202
+ base_url: init
203
+ .get_item("baseURL")?
204
+ .map(|v| v.extract::<String>())
205
+ .transpose()?
206
+ .map(|v| v.parse::<url::Url>())
207
+ .transpose()
208
+ .map_err(deno_urlpattern::Error::Url)
209
+ .map_err(Error)?,
210
+ })
211
+ }
212
+ },
213
+ None => deno_urlpattern::UrlPatternMatchInput::Init(
214
+ deno_urlpattern::UrlPatternInit::default(),
215
+ ),
216
+ };
217
+ Ok(self.0.test(input).map_err(Error)?)
218
+ }
219
+
220
+ #[pyo3(signature = (input=None, baseURL=None))]
221
+ fn exec<'py>(
222
+ &self,
223
+ py: Python<'py>,
224
+ input: Option<&Bound<'py, PyAny>>,
225
+ baseURL: Option<&Bound<'py, PyString>>,
226
+ ) -> PyResult<Option<UrlPatternResult<'py>>> {
227
+ let urlpattern_input: Option<UrlPatternInput> = input.map(|i| i.extract()).transpose()?;
228
+ let input: deno_urlpattern::UrlPatternMatchInput = match &urlpattern_input {
229
+ Some(input) => match input {
230
+ UrlPatternInput::String(input) => match baseURL {
231
+ Some(base_url) => {
232
+ let base_url = match url::Url::parse(base_url.to_str()?) {
233
+ Ok(url) => url,
234
+ Err(_) => return Ok(None),
235
+ };
236
+ deno_urlpattern::UrlPatternMatchInput::Url(
237
+ match url::Url::options()
238
+ .base_url(Some(&base_url))
239
+ .parse(input.as_ref())
240
+ {
241
+ Ok(url) => url,
242
+ Err(_) => return Ok(None),
243
+ },
244
+ )
245
+ }
246
+ None => deno_urlpattern::UrlPatternMatchInput::Url(
247
+ match input.parse::<url::Url>() {
248
+ Ok(url) => url,
249
+ Err(_) => return Ok(None),
250
+ },
251
+ ),
252
+ },
253
+ UrlPatternInput::Init(init) => {
254
+ if let Some(_) = baseURL {
255
+ return Err(PyTypeError::new_err("cannot use dict input with baseURL"));
256
+ }
257
+
258
+ deno_urlpattern::UrlPatternMatchInput::Init(deno_urlpattern::UrlPatternInit {
259
+ protocol: init
260
+ .get_item("protocol")?
261
+ .map(|v| v.extract::<String>())
262
+ .transpose()?,
263
+ username: init
264
+ .get_item("username")?
265
+ .map(|v| v.extract::<String>())
266
+ .transpose()?,
267
+ password: init
268
+ .get_item("password")?
269
+ .map(|v| v.extract::<String>())
270
+ .transpose()?,
271
+ hostname: init
272
+ .get_item("hostname")?
273
+ .map(|v| v.extract::<String>())
274
+ .transpose()?,
275
+ port: init
276
+ .get_item("port")?
277
+ .map(|v| v.extract::<String>())
278
+ .transpose()?,
279
+ pathname: init
280
+ .get_item("pathname")?
281
+ .map(|v| v.extract::<String>())
282
+ .transpose()?,
283
+ search: init
284
+ .get_item("search")?
285
+ .map(|v| v.extract::<String>())
286
+ .transpose()?,
287
+ hash: init
288
+ .get_item("hash")?
289
+ .map(|v| v.extract::<String>())
290
+ .transpose()?,
291
+ base_url: init
292
+ .get_item("baseURL")?
293
+ .map(|v| v.extract::<String>())
294
+ .transpose()?
295
+ .map(|v| v.parse::<url::Url>())
296
+ .transpose()
297
+ .map_err(deno_urlpattern::Error::Url)
298
+ .map_err(Error)?,
299
+ })
300
+ }
301
+ },
302
+ None => deno_urlpattern::UrlPatternMatchInput::Init(
303
+ deno_urlpattern::UrlPatternInit::default(),
304
+ ),
305
+ };
306
+
307
+ let Some(result) = self.0.exec(input).map_err(Error)? else {
308
+ return Ok(None);
309
+ };
310
+
311
+ Ok(Some(UrlPatternResult {
312
+ inputs: {
313
+ let mut vec = Vec::new();
314
+ vec.push(
315
+ urlpattern_input.unwrap_or(UrlPatternInput::Init(PyDict::new(py).into_bound())),
316
+ );
317
+ if let Some(base_url) = baseURL {
318
+ vec.push(UrlPatternInput::String(base_url.to_string()));
319
+ }
320
+ vec
321
+ },
322
+ protocol: UrlPatternComponentResult {
323
+ input: result.protocol.input,
324
+ groups: result.protocol.groups,
325
+ },
326
+ username: UrlPatternComponentResult {
327
+ input: result.username.input,
328
+ groups: result.username.groups,
329
+ },
330
+ password: UrlPatternComponentResult {
331
+ input: result.password.input,
332
+ groups: result.password.groups,
333
+ },
334
+ hostname: UrlPatternComponentResult {
335
+ input: result.hostname.input,
336
+ groups: result.hostname.groups,
337
+ },
338
+ port: UrlPatternComponentResult {
339
+ input: result.port.input,
340
+ groups: result.port.groups,
341
+ },
342
+ pathname: UrlPatternComponentResult {
343
+ input: result.pathname.input,
344
+ groups: result.pathname.groups,
345
+ },
346
+ search: UrlPatternComponentResult {
347
+ input: result.search.input,
348
+ groups: result.search.groups,
349
+ },
350
+ hash: UrlPatternComponentResult {
351
+ input: result.hash.input,
352
+ groups: result.hash.groups,
353
+ },
354
+ }))
355
+ }
356
+
357
+ #[getter]
358
+ fn protocol(&self) -> PyResult<&str> {
359
+ Ok(self.0.protocol())
360
+ }
361
+
362
+ #[getter]
363
+ fn username(&self) -> PyResult<&str> {
364
+ Ok(self.0.username())
365
+ }
366
+
367
+ #[getter]
368
+ fn password(&self) -> PyResult<&str> {
369
+ Ok(self.0.password())
370
+ }
371
+
372
+ #[getter]
373
+ fn hostname(&self) -> PyResult<&str> {
374
+ Ok(self.0.hostname())
375
+ }
376
+
377
+ #[getter]
378
+ fn port(&self) -> PyResult<&str> {
379
+ Ok(self.0.port())
380
+ }
381
+
382
+ #[getter]
383
+ fn pathname(&self) -> PyResult<&str> {
384
+ Ok(self.0.pathname())
385
+ }
386
+
387
+ #[getter]
388
+ fn search(&self) -> PyResult<&str> {
389
+ Ok(self.0.search())
390
+ }
391
+
392
+ #[getter]
393
+ fn hash(&self) -> PyResult<&str> {
394
+ Ok(self.0.hash())
395
+ }
396
+
397
+ #[getter(hasRegExpGroups)]
398
+ fn has_regexp_groups(&self) -> PyResult<bool> {
399
+ Ok(self.0.has_regexp_groups())
400
+ }
401
+ }
402
+
403
+ #[derive(FromPyObject)]
404
+ enum UrlPatternInput<'py> {
405
+ String(String),
406
+ Init(Bound<'py, PyDict>),
407
+ }
408
+
409
+ struct UrlPatternResult<'py> {
410
+ inputs: Vec<UrlPatternInput<'py>>,
411
+ protocol: UrlPatternComponentResult,
412
+ username: UrlPatternComponentResult,
413
+ password: UrlPatternComponentResult,
414
+ hostname: UrlPatternComponentResult,
415
+ port: UrlPatternComponentResult,
416
+ pathname: UrlPatternComponentResult,
417
+ search: UrlPatternComponentResult,
418
+ hash: UrlPatternComponentResult,
419
+ }
420
+
421
+ impl<'py> IntoPyObject<'py> for UrlPatternResult<'py> {
422
+ type Target = PyDict;
423
+ type Output = Bound<'py, Self::Target>;
424
+ type Error = std::convert::Infallible;
425
+
426
+ fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
427
+ let dict = PyDict::new(py);
428
+
429
+ let inputs = PyList::empty(py);
430
+ for input in self.inputs {
431
+ match input {
432
+ UrlPatternInput::String(string) => {
433
+ inputs.append(string).unwrap();
434
+ }
435
+ UrlPatternInput::Init(init) => {
436
+ inputs.append(init).unwrap();
437
+ }
438
+ }
439
+ }
440
+
441
+ dict.set_item("inputs", inputs).unwrap();
442
+ dict.set_item("protocol", self.protocol).unwrap();
443
+ dict.set_item("username", self.username).unwrap();
444
+ dict.set_item("password", self.password).unwrap();
445
+ dict.set_item("hostname", self.hostname).unwrap();
446
+ dict.set_item("port", self.port).unwrap();
447
+ dict.set_item("pathname", self.pathname).unwrap();
448
+ dict.set_item("search", self.search).unwrap();
449
+ dict.set_item("hash", self.hash).unwrap();
450
+ Ok(dict.into_bound())
451
+ }
452
+ }
453
+
454
+ #[derive(IntoPyObject, IntoPyObjectRef)]
455
+ struct UrlPatternComponentResult {
456
+ input: String,
457
+ groups: HashMap<String, Option<String>>,
458
+ }
459
+
460
+ struct Error(deno_urlpattern::Error);
461
+
462
+ impl From<Error> for PyErr {
463
+ fn from(error: Error) -> Self {
464
+ PyValueError::new_err(error.0.to_string())
465
+ }
466
+ }
467
+
468
+ impl From<deno_urlpattern::Error> for Error {
469
+ fn from(other: deno_urlpattern::Error) -> Self {
470
+ Self(other)
471
+ }
472
+ }
473
+
474
+ /// A Python module implemented in Rust.
475
+ #[pymodule]
476
+ mod urlpattern {
477
+ #[pymodule_export]
478
+ use super::UrlPattern;
479
+ }
@@ -19,7 +19,7 @@ urlpatterntestdata = json.loads(
19
19
 
20
20
  @pytest.mark.parametrize("entry", urlpatterntestdata)
21
21
  def test(entry):
22
- if entry["pattern"] == [{"pathname": "*{}**?"}]:
22
+ if entry["pattern"] == [{"pathname": "*{}**?"}] or entry["pattern"] == ["((?R)):"]:
23
23
  pytest.skip("unsupported in the implementation")
24
24
 
25
25
  if entry.get("expected_obj") == "error":
@@ -3118,5 +3118,41 @@
3118
3118
  "expected_match": {
3119
3119
  "hostname": { "input": "localhost", "groups": { "domain" : "localhost"} }
3120
3120
  }
3121
+ },
3122
+ {
3123
+ "pattern": ["((?R)):"],
3124
+ "expected_obj": "error"
3125
+ },
3126
+ {
3127
+ "pattern": ["(\\H):"],
3128
+ "expected_obj": "error"
3129
+ },
3130
+ {
3131
+ "pattern": [
3132
+ {"pathname": "/:foo((?<x>a))"}
3133
+ ],
3134
+ "inputs": [
3135
+ {"pathname": "/a"}
3136
+ ],
3137
+ "expected_match": {
3138
+ "pathname": {
3139
+ "input": "/a",
3140
+ "groups": {"foo": "a"}
3141
+ }
3142
+ }
3143
+ },
3144
+ {
3145
+ "pattern": [
3146
+ {"pathname": "/foo/(bar(?<x>baz))"}
3147
+ ],
3148
+ "inputs": [
3149
+ {"pathname": "/foo/barbaz"}
3150
+ ],
3151
+ "expected_match": {
3152
+ "pathname": {
3153
+ "input": "/foo/barbaz",
3154
+ "groups": {"0": "barbaz"}
3155
+ }
3156
+ }
3121
3157
  }
3122
3158
  ]
@@ -76,6 +76,8 @@ class URLPattern:
76
76
  def search(self) -> str: ...
77
77
  @property
78
78
  def hash(self) -> str: ...
79
+ @property
80
+ def hasRegExpGroups(self) -> bool: ...
79
81
 
80
82
  class URLPatternInit(TypedDict, total=False):
81
83
  protocol: str
@@ -1,8 +0,0 @@
1
- {
2
- "python.defaultInterpreterPath": "${workspaceFolder}/.venv",
3
- "python.testing.pytestArgs": [
4
- "tests"
5
- ],
6
- "python.testing.unittestEnabled": false,
7
- "python.testing.pytestEnabled": true
8
- }
@@ -1,364 +0,0 @@
1
- #![allow(non_snake_case)]
2
-
3
- use std::borrow::Cow;
4
- use std::collections::HashMap;
5
-
6
- use pyo3::{
7
- BoundObject,
8
- exceptions::PyValueError,
9
- prelude::*,
10
- types::{PyDict, PyList},
11
- };
12
-
13
- #[pyclass(name = "URLPattern")]
14
- struct UrlPattern(deno_urlpattern::UrlPattern);
15
-
16
- #[pymethods]
17
- impl UrlPattern {
18
- #[new]
19
- #[pyo3(signature = (input=None, baseURL=None, options=None))]
20
- pub fn new(
21
- input: Option<UrlPatternInput>,
22
- baseURL: Option<&Bound<'_, PyAny>>,
23
- options: Option<&Bound<'_, PyDict>>,
24
- ) -> PyResult<Self> {
25
- let (base_url, options) = match baseURL {
26
- Some(value) => {
27
- if let Ok(options_dict) = value.cast::<PyDict>() {
28
- (None, Some(options_dict))
29
- } else if value.is_none() {
30
- (None, options)
31
- } else {
32
- (Some(value.extract::<String>()?), options)
33
- }
34
- }
35
- None => (None, options),
36
- };
37
-
38
- let string_or_init_input = match input {
39
- Some(input) => deno_urlpattern::quirks::StringOrInit::try_from(input)?,
40
- None => deno_urlpattern::quirks::StringOrInit::Init(
41
- deno_urlpattern::quirks::UrlPatternInit::default(),
42
- ),
43
- };
44
- let options = if let Some(options) = options {
45
- deno_urlpattern::UrlPatternOptions {
46
- ignore_case: options
47
- .get_item("ignoreCase")?
48
- .map(|v| v.extract::<bool>())
49
- .transpose()?
50
- .unwrap_or(false),
51
- ..deno_urlpattern::UrlPatternOptions::default()
52
- }
53
- } else {
54
- deno_urlpattern::UrlPatternOptions::default()
55
- };
56
- Ok(UrlPattern(
57
- <deno_urlpattern::UrlPattern>::parse(
58
- deno_urlpattern::quirks::process_construct_pattern_input(
59
- string_or_init_input,
60
- base_url.as_deref(),
61
- )
62
- .map_err(Error)?,
63
- options,
64
- )
65
- .map_err(Error)?,
66
- ))
67
- }
68
-
69
- pub fn __repr__(&self, py: Python) -> String {
70
- let dict = PyDict::new(py);
71
- dict.set_item("protocol", self.0.protocol()).unwrap();
72
- dict.set_item("username", self.0.username()).unwrap();
73
- dict.set_item("password", self.0.password()).unwrap();
74
- dict.set_item("hostname", self.0.hostname()).unwrap();
75
- dict.set_item("port", self.0.port()).unwrap();
76
- dict.set_item("pathname", self.0.pathname()).unwrap();
77
- dict.set_item("search", self.0.search()).unwrap();
78
- dict.set_item("hash", self.0.hash()).unwrap();
79
- format!("URLPattern({})", dict)
80
- }
81
-
82
- #[pyo3(signature = (input=None, baseURL=None))]
83
- pub fn test(&self, input: Option<UrlPatternInput>, baseURL: Option<&str>) -> PyResult<bool> {
84
- let string_or_init_input = match input {
85
- Some(input) => deno_urlpattern::quirks::StringOrInit::try_from(input)?,
86
- None => deno_urlpattern::quirks::StringOrInit::Init(
87
- deno_urlpattern::quirks::UrlPatternInit::default(),
88
- ),
89
- };
90
- let Some((match_input, _)) =
91
- deno_urlpattern::quirks::process_match_input(string_or_init_input, baseURL)
92
- .map_err(Error)?
93
- else {
94
- return Ok(false);
95
- };
96
- Ok(self.0.test(match_input).map_err(Error)?)
97
- }
98
-
99
- #[pyo3(signature = (input=None, baseURL=None))]
100
- pub fn exec(
101
- &self,
102
- input: Option<UrlPatternInput>,
103
- baseURL: Option<&str>,
104
- ) -> PyResult<Option<UrlPatternResult>> {
105
- let string_or_init_input = match input {
106
- Some(input) => deno_urlpattern::quirks::StringOrInit::try_from(input)?,
107
- None => deno_urlpattern::quirks::StringOrInit::Init(
108
- deno_urlpattern::quirks::UrlPatternInit::default(),
109
- ),
110
- };
111
- let Some((match_input, inputs)) =
112
- deno_urlpattern::quirks::process_match_input(string_or_init_input, baseURL)
113
- .map_err(Error)?
114
- else {
115
- return Ok(None);
116
- };
117
- let Some(result) = self.0.exec(match_input).map_err(Error)? else {
118
- return Ok(None);
119
- };
120
-
121
- Ok(Some(UrlPatternResult {
122
- inputs,
123
- protocol: UrlPatternComponentResult {
124
- input: result.protocol.input,
125
- groups: result.protocol.groups,
126
- },
127
- username: UrlPatternComponentResult {
128
- input: result.username.input,
129
- groups: result.username.groups,
130
- },
131
- password: UrlPatternComponentResult {
132
- input: result.password.input,
133
- groups: result.password.groups,
134
- },
135
- hostname: UrlPatternComponentResult {
136
- input: result.hostname.input,
137
- groups: result.hostname.groups,
138
- },
139
- port: UrlPatternComponentResult {
140
- input: result.port.input,
141
- groups: result.port.groups,
142
- },
143
- pathname: UrlPatternComponentResult {
144
- input: result.pathname.input,
145
- groups: result.pathname.groups,
146
- },
147
- search: UrlPatternComponentResult {
148
- input: result.search.input,
149
- groups: result.search.groups,
150
- },
151
- hash: UrlPatternComponentResult {
152
- input: result.hash.input,
153
- groups: result.hash.groups,
154
- },
155
- }))
156
- }
157
-
158
- #[getter]
159
- pub fn get_protocol(&self) -> PyResult<&str> {
160
- Ok(self.0.protocol())
161
- }
162
-
163
- #[getter]
164
- pub fn get_username(&self) -> PyResult<&str> {
165
- Ok(self.0.username())
166
- }
167
-
168
- #[getter]
169
- pub fn get_password(&self) -> PyResult<&str> {
170
- Ok(self.0.password())
171
- }
172
-
173
- #[getter]
174
- pub fn get_hostname(&self) -> PyResult<&str> {
175
- Ok(self.0.hostname())
176
- }
177
-
178
- #[getter]
179
- pub fn get_port(&self) -> PyResult<&str> {
180
- Ok(self.0.port())
181
- }
182
-
183
- #[getter]
184
- pub fn get_pathname(&self) -> PyResult<&str> {
185
- Ok(self.0.pathname())
186
- }
187
-
188
- #[getter]
189
- pub fn get_search(&self) -> PyResult<&str> {
190
- Ok(self.0.search())
191
- }
192
-
193
- #[getter]
194
- pub fn get_hash(&self) -> PyResult<&str> {
195
- Ok(self.0.hash())
196
- }
197
- }
198
-
199
- #[derive(FromPyObject)]
200
- pub enum UrlPatternInput<'py> {
201
- String(String),
202
- Init(Bound<'py, PyDict>),
203
- }
204
-
205
- impl<'py> TryFrom<UrlPatternInput<'py>> for deno_urlpattern::quirks::StringOrInit<'static> {
206
- type Error = pyo3::PyErr;
207
-
208
- fn try_from(input: UrlPatternInput<'py>) -> Result<Self, Self::Error> {
209
- Ok(match input {
210
- UrlPatternInput::String(pattern) => {
211
- deno_urlpattern::quirks::StringOrInit::String(Cow::Owned(pattern))
212
- }
213
- UrlPatternInput::Init(init) => deno_urlpattern::quirks::StringOrInit::Init(
214
- deno_urlpattern::quirks::UrlPatternInit {
215
- protocol: init
216
- .get_item("protocol")?
217
- .map(|v| v.extract::<String>())
218
- .transpose()?,
219
- username: init
220
- .get_item("username")?
221
- .map(|v| v.extract::<String>())
222
- .transpose()?,
223
- password: init
224
- .get_item("password")?
225
- .map(|v| v.extract::<String>())
226
- .transpose()?,
227
- hostname: init
228
- .get_item("hostname")?
229
- .map(|v| v.extract::<String>())
230
- .transpose()?,
231
- port: init
232
- .get_item("port")?
233
- .map(|v| v.extract::<String>())
234
- .transpose()?,
235
- pathname: init
236
- .get_item("pathname")?
237
- .map(|v| v.extract::<String>())
238
- .transpose()?,
239
- search: init
240
- .get_item("search")?
241
- .map(|v| v.extract::<String>())
242
- .transpose()?,
243
- hash: init
244
- .get_item("hash")?
245
- .map(|v| v.extract::<String>())
246
- .transpose()?,
247
- base_url: init
248
- .get_item("baseURL")?
249
- .map(|v| v.extract::<String>())
250
- .transpose()?,
251
- },
252
- ),
253
- })
254
- }
255
- }
256
-
257
- pub struct UrlPatternResult {
258
- pub inputs: (
259
- deno_urlpattern::quirks::StringOrInit<'static>,
260
- Option<String>,
261
- ),
262
- pub protocol: UrlPatternComponentResult,
263
- pub username: UrlPatternComponentResult,
264
- pub password: UrlPatternComponentResult,
265
- pub hostname: UrlPatternComponentResult,
266
- pub port: UrlPatternComponentResult,
267
- pub pathname: UrlPatternComponentResult,
268
- pub search: UrlPatternComponentResult,
269
- pub hash: UrlPatternComponentResult,
270
- }
271
-
272
- impl<'py> IntoPyObject<'py> for UrlPatternResult {
273
- type Target = PyDict;
274
- type Output = Bound<'py, Self::Target>;
275
- type Error = std::convert::Infallible;
276
-
277
- fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
278
- let dict = PyDict::new(py);
279
-
280
- let (string_or_init, base_url) = self.inputs;
281
- let list = PyList::empty(py);
282
-
283
- match string_or_init {
284
- deno_urlpattern::quirks::StringOrInit::String(string) => {
285
- list.append(string.into_owned()).unwrap();
286
- }
287
- deno_urlpattern::quirks::StringOrInit::Init(init) => {
288
- let init_dict = PyDict::new(py);
289
- if let Some(protocol) = init.protocol {
290
- init_dict.set_item("protocol", protocol).unwrap();
291
- }
292
- if let Some(username) = init.username {
293
- init_dict.set_item("username", username).unwrap();
294
- }
295
- if let Some(password) = init.password {
296
- init_dict.set_item("password", password).unwrap();
297
- }
298
- if let Some(hostname) = init.hostname {
299
- init_dict.set_item("hostname", hostname).unwrap();
300
- }
301
- if let Some(port) = init.port {
302
- init_dict.set_item("port", port).unwrap();
303
- }
304
- if let Some(pathname) = init.pathname {
305
- init_dict.set_item("pathname", pathname).unwrap();
306
- }
307
- if let Some(search) = init.search {
308
- init_dict.set_item("search", search).unwrap();
309
- }
310
- if let Some(hash) = init.hash {
311
- init_dict.set_item("hash", hash).unwrap();
312
- }
313
- if let Some(base_url) = init.base_url {
314
- init_dict.set_item("baseURL", base_url).unwrap();
315
- }
316
- list.append(init_dict).unwrap();
317
- }
318
- }
319
-
320
- if let Some(base_url) = base_url {
321
- list.append(base_url).unwrap();
322
- }
323
-
324
- dict.set_item("inputs", list).unwrap();
325
-
326
- dict.set_item("protocol", self.protocol).unwrap();
327
- dict.set_item("username", self.username).unwrap();
328
- dict.set_item("password", self.password).unwrap();
329
- dict.set_item("hostname", self.hostname).unwrap();
330
- dict.set_item("port", self.port).unwrap();
331
- dict.set_item("pathname", self.pathname).unwrap();
332
- dict.set_item("search", self.search).unwrap();
333
- dict.set_item("hash", self.hash).unwrap();
334
-
335
- Ok(dict.into_bound())
336
- }
337
- }
338
-
339
- #[derive(IntoPyObject, IntoPyObjectRef)]
340
- pub struct UrlPatternComponentResult {
341
- input: String,
342
- groups: HashMap<String, Option<String>>,
343
- }
344
-
345
- pub struct Error(deno_urlpattern::Error);
346
-
347
- impl From<Error> for PyErr {
348
- fn from(error: Error) -> Self {
349
- PyValueError::new_err(error.0.to_string())
350
- }
351
- }
352
-
353
- impl From<deno_urlpattern::Error> for Error {
354
- fn from(other: deno_urlpattern::Error) -> Self {
355
- Self(other)
356
- }
357
- }
358
-
359
- /// A Python module implemented in Rust.
360
- #[pymodule]
361
- mod urlpattern {
362
- #[pymodule_export]
363
- use super::UrlPattern;
364
- }
File without changes
File without changes
File without changes