├── .github ├── FUNDING.yml └── workflows │ ├── checks.yml │ └── depsbot.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── auto.ts ├── mod.ts ├── rustfmt.toml ├── scripts └── build.ts ├── wasm ├── lib.rs └── wasm.js └── webidl.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: denosaurs 2 | github: denosaurs 3 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: check 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout sources 10 | uses: actions/checkout@v2 11 | 12 | - name: Setup latest deno version 13 | uses: denolib/setup-deno@v2 14 | with: 15 | deno-version: v1.x 16 | 17 | - name: Run deno fmt 18 | run: deno fmt --check 19 | 20 | - name: Run deno lint 21 | run: deno lint --unstable 22 | -------------------------------------------------------------------------------- /.github/workflows/depsbot.yml: -------------------------------------------------------------------------------- 1 | name: depsbot 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | schedule: 9 | - cron: "0 0 */2 * *" 10 | 11 | jobs: 12 | run: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v2 17 | 18 | - name: Run depsbot 19 | uses: denosaurs/depsbot@master 20 | with: 21 | github_token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # cargo build 2 | target/ 3 | # wasm-pack 4 | pkg/ 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog], and this project adheres to 6 | [Semantic Versioning]. 7 | 8 | ## [0.0.1] - 2021-09-08 9 | 10 | ### Features 11 | 12 | - inital commit ([`f3083f2`]) 13 | 14 | [keep a changelog]: https://keepachangelog.com/en/1.0.0/ 15 | [semantic versioning]: https://semver.org/spec/v2.0.0.html 16 | [0.0.1]: https://github.com/denosaurs/urlpattern/compare/0.0.1 17 | [`f3083f2`]: https://github.com/denosaurs/urlpattern/commit/f3083f2549ef9c318a14a2afe616be8d93df6d85 18 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "bumpalo" 16 | version = "3.7.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" 19 | 20 | [[package]] 21 | name = "cfg-if" 22 | version = "1.0.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 25 | 26 | [[package]] 27 | name = "convert_case" 28 | version = "0.4.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 31 | 32 | [[package]] 33 | name = "derive_more" 34 | version = "0.99.16" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" 37 | dependencies = [ 38 | "convert_case", 39 | "proc-macro2", 40 | "quote", 41 | "rustc_version", 42 | "syn", 43 | ] 44 | 45 | [[package]] 46 | name = "form_urlencoded" 47 | version = "1.0.1" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 50 | dependencies = [ 51 | "matches", 52 | "percent-encoding", 53 | ] 54 | 55 | [[package]] 56 | name = "idna" 57 | version = "0.2.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 60 | dependencies = [ 61 | "matches", 62 | "unicode-bidi", 63 | "unicode-normalization", 64 | ] 65 | 66 | [[package]] 67 | name = "itoa" 68 | version = "0.4.8" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 71 | 72 | [[package]] 73 | name = "lazy_static" 74 | version = "1.4.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 77 | 78 | [[package]] 79 | name = "log" 80 | version = "0.4.14" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 83 | dependencies = [ 84 | "cfg-if", 85 | ] 86 | 87 | [[package]] 88 | name = "matches" 89 | version = "0.1.9" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 92 | 93 | [[package]] 94 | name = "memchr" 95 | version = "2.4.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 98 | 99 | [[package]] 100 | name = "percent-encoding" 101 | version = "2.1.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 104 | 105 | [[package]] 106 | name = "pest" 107 | version = "2.1.3" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 110 | dependencies = [ 111 | "ucd-trie", 112 | ] 113 | 114 | [[package]] 115 | name = "proc-macro2" 116 | version = "1.0.29" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 119 | dependencies = [ 120 | "unicode-xid", 121 | ] 122 | 123 | [[package]] 124 | name = "quote" 125 | version = "1.0.9" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 128 | dependencies = [ 129 | "proc-macro2", 130 | ] 131 | 132 | [[package]] 133 | name = "regex" 134 | version = "1.5.4" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 137 | dependencies = [ 138 | "aho-corasick", 139 | "memchr", 140 | "regex-syntax", 141 | ] 142 | 143 | [[package]] 144 | name = "regex-syntax" 145 | version = "0.6.25" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 148 | 149 | [[package]] 150 | name = "rustc_version" 151 | version = "0.3.3" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" 154 | dependencies = [ 155 | "semver", 156 | ] 157 | 158 | [[package]] 159 | name = "ryu" 160 | version = "1.0.5" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 163 | 164 | [[package]] 165 | name = "semver" 166 | version = "0.11.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" 169 | dependencies = [ 170 | "semver-parser", 171 | ] 172 | 173 | [[package]] 174 | name = "semver-parser" 175 | version = "0.10.2" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" 178 | dependencies = [ 179 | "pest", 180 | ] 181 | 182 | [[package]] 183 | name = "serde" 184 | version = "1.0.130" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 187 | dependencies = [ 188 | "serde_derive", 189 | ] 190 | 191 | [[package]] 192 | name = "serde_derive" 193 | version = "1.0.130" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 196 | dependencies = [ 197 | "proc-macro2", 198 | "quote", 199 | "syn", 200 | ] 201 | 202 | [[package]] 203 | name = "serde_json" 204 | version = "1.0.67" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" 207 | dependencies = [ 208 | "itoa", 209 | "ryu", 210 | "serde", 211 | ] 212 | 213 | [[package]] 214 | name = "syn" 215 | version = "1.0.76" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" 218 | dependencies = [ 219 | "proc-macro2", 220 | "quote", 221 | "unicode-xid", 222 | ] 223 | 224 | [[package]] 225 | name = "tinyvec" 226 | version = "1.3.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" 229 | dependencies = [ 230 | "tinyvec_macros", 231 | ] 232 | 233 | [[package]] 234 | name = "tinyvec_macros" 235 | version = "0.1.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 238 | 239 | [[package]] 240 | name = "ucd-trie" 241 | version = "0.1.3" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 244 | 245 | [[package]] 246 | name = "unic-char-property" 247 | version = "0.9.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" 250 | dependencies = [ 251 | "unic-char-range", 252 | ] 253 | 254 | [[package]] 255 | name = "unic-char-range" 256 | version = "0.9.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" 259 | 260 | [[package]] 261 | name = "unic-common" 262 | version = "0.9.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" 265 | 266 | [[package]] 267 | name = "unic-ucd-ident" 268 | version = "0.9.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" 271 | dependencies = [ 272 | "unic-char-property", 273 | "unic-char-range", 274 | "unic-ucd-version", 275 | ] 276 | 277 | [[package]] 278 | name = "unic-ucd-version" 279 | version = "0.9.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" 282 | dependencies = [ 283 | "unic-common", 284 | ] 285 | 286 | [[package]] 287 | name = "unicode-bidi" 288 | version = "0.3.6" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" 291 | 292 | [[package]] 293 | name = "unicode-normalization" 294 | version = "0.1.19" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 297 | dependencies = [ 298 | "tinyvec", 299 | ] 300 | 301 | [[package]] 302 | name = "unicode-xid" 303 | version = "0.2.2" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 306 | 307 | [[package]] 308 | name = "url" 309 | version = "2.2.2" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 312 | dependencies = [ 313 | "form_urlencoded", 314 | "idna", 315 | "matches", 316 | "percent-encoding", 317 | ] 318 | 319 | [[package]] 320 | name = "urlpattern" 321 | version = "0.1.0" 322 | dependencies = [ 323 | "urlpattern 0.1.3", 324 | "wasm-bindgen", 325 | ] 326 | 327 | [[package]] 328 | name = "urlpattern" 329 | version = "0.1.3" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "8f7decaf7b744aab3251b934f4b92b2d1db77d12b23d76730b55ac2c80956ba9" 332 | dependencies = [ 333 | "derive_more", 334 | "regex", 335 | "serde", 336 | "unic-ucd-ident", 337 | "url", 338 | ] 339 | 340 | [[package]] 341 | name = "wasm-bindgen" 342 | version = "0.2.78" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 345 | dependencies = [ 346 | "cfg-if", 347 | "serde", 348 | "serde_json", 349 | "wasm-bindgen-macro", 350 | ] 351 | 352 | [[package]] 353 | name = "wasm-bindgen-backend" 354 | version = "0.2.78" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 357 | dependencies = [ 358 | "bumpalo", 359 | "lazy_static", 360 | "log", 361 | "proc-macro2", 362 | "quote", 363 | "syn", 364 | "wasm-bindgen-shared", 365 | ] 366 | 367 | [[package]] 368 | name = "wasm-bindgen-macro" 369 | version = "0.2.78" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 372 | dependencies = [ 373 | "quote", 374 | "wasm-bindgen-macro-support", 375 | ] 376 | 377 | [[package]] 378 | name = "wasm-bindgen-macro-support" 379 | version = "0.2.78" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 382 | dependencies = [ 383 | "proc-macro2", 384 | "quote", 385 | "syn", 386 | "wasm-bindgen-backend", 387 | "wasm-bindgen-shared", 388 | ] 389 | 390 | [[package]] 391 | name = "wasm-bindgen-shared" 392 | version = "0.2.78" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 395 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "urlpattern" 3 | description = "A urlpattern polyfill for deno and the web" 4 | repository = "https://github.com/denosaurs/urlpattern" 5 | license = "MIT" 6 | version = "0.1.0" 7 | authors = ["Elias Sjögreen"] 8 | edition = "2021" 9 | publish = false 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | path = "wasm/lib.rs" 14 | 15 | [dependencies] 16 | wasm-bindgen = { version = "0.2.78", features = ["serde-serialize"] } 17 | urlpattern = "0.1.3" 18 | 19 | [profile.release] 20 | panic = "abort" 21 | opt-level = "z" 22 | lto = true 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 the denosaurs team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # URLPattern polyfill 2 | 3 | This module is a polyfill implementing the 4 | [`URLPattern` web API](https://github.com/WICG/urlpattern) api in wasm and js. 5 | We use the crate [rust-urlpattern](https://github.com/denoland/rust-urlpattern) 6 | from [denoland](https://github.com/denoland/rust-urlpattern) which is the same 7 | as is used internally in the deno runtime. 8 | 9 | The module works both in deno (for when the `unstable` flag is not passed as is 10 | the case until the deno api is stabilized) and in the browser. 11 | 12 | ## Usage 13 | 14 | Using `URLPattern` is as simple as: 15 | 16 | ```ts 17 | import { URLPattern } from "https://deno.land/x/urlpattern/mod.ts"; 18 | 19 | const pattern = new URLPattern("/:some/:pattern", "https://example.com/some/pattern) 20 | ``` 21 | 22 | Or in case you want to automatically register the `URLPattern` object globally 23 | to the `window` object (in case it does not already exist): 24 | 25 | ```ts 26 | import "https://deno.land/x/urlpattern/auto.ts"; 27 | 28 | const pattern = new URLPattern("/:some/:pattern", "https://example.com/some/pattern) 29 | ``` 30 | 31 | ## Maintainers 32 | 33 | - Elias Sjögreen ([@eliassjogreen](https://github.com/eliassjogreen)) 34 | 35 | ## Other 36 | 37 | ### Contribution 38 | 39 | Pull request, issues and feedback are very welcome. Code style is formatted with 40 | `deno fmt` and commit messages are done following Conventional Commits spec. 41 | 42 | ### Licence 43 | 44 | Copyright 2021, the denosaurs team. All rights reserved. MIT license. 45 | -------------------------------------------------------------------------------- /auto.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file 2 | 3 | if (!("URLPattern" in window)) { 4 | //@ts-ignore 5 | window.URLPattern = (await import("./mod.ts")).URLPattern; 6 | } 7 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. 2 | // Copyright 2021 the denosaurs team. All rights reserved. MIT license. 3 | // deno-lint-ignore-file 4 | 5 | import init, { 6 | source, 7 | urlpattern_parse as urlpatternParse, 8 | urlpattern_process_match_input as urlpatternProcessMatchInput, 9 | } from "./wasm/wasm.js"; 10 | 11 | import * as webidl from "./webidl.js"; 12 | 13 | await init(source); 14 | 15 | export interface URLPatternInit { 16 | protocol?: string; 17 | username?: string; 18 | password?: string; 19 | hostname?: string; 20 | port?: string; 21 | pathname?: string; 22 | search?: string; 23 | hash?: string; 24 | baseURL?: string; 25 | } 26 | 27 | export type URLPatternInput = string | URLPatternInit; 28 | 29 | export interface URLPatternComponentResult { 30 | input: string; 31 | groups: Record; 32 | } 33 | 34 | /** `URLPatternResult` is the object returned from `URLPattern.match`. */ 35 | export interface URLPatternResult { 36 | /** The inputs provided when matching. */ 37 | inputs: [URLPatternInit] | [URLPatternInit, string]; 38 | 39 | /** The matched result for the `protocol` matcher. */ 40 | protocol: URLPatternComponentResult; 41 | /** The matched result for the `username` matcher. */ 42 | username: URLPatternComponentResult; 43 | /** The matched result for the `password` matcher. */ 44 | password: URLPatternComponentResult; 45 | /** The matched result for the `hostname` matcher. */ 46 | hostname: URLPatternComponentResult; 47 | /** The matched result for the `port` matcher. */ 48 | port: URLPatternComponentResult; 49 | /** The matched result for the `pathname` matcher. */ 50 | pathname: URLPatternComponentResult; 51 | /** The matched result for the `search` matcher. */ 52 | search: URLPatternComponentResult; 53 | /** The matched result for the `hash` matcher. */ 54 | hash: URLPatternComponentResult; 55 | } 56 | 57 | const _components = Symbol("components"); 58 | 59 | /** 60 | * The URLPattern API provides a web platform primitive for matching URLs based 61 | * on a convenient pattern syntax. 62 | * 63 | * The syntax is based on path-to-regexp. Wildcards, named capture groups, 64 | * regular groups, and group modifiers are all supported. 65 | * 66 | * ```ts 67 | * // Specify the pattern as structured data. 68 | * const pattern = new URLPattern({ pathname: "/users/:user" }); 69 | * const match = pattern.match("/users/joe"); 70 | * console.log(match.pathname.groups.user); // joe 71 | * ``` 72 | * 73 | * ```ts 74 | * // Specify a fully qualified string pattern. 75 | * const pattern = new URLPattern("https://example.com/books/:id"); 76 | * console.log(pattern.test("https://example.com/books/123")); // true 77 | * console.log(pattern.test("https://deno.land/books/123")); // false 78 | * ``` 79 | * 80 | * ```ts 81 | * // Specify a relative string pattern with a base URL. 82 | * const pattern = new URLPattern("/:article", "https://blog.example.com"); 83 | * console.log(pattern.test("https://blog.example.com/article")); // true 84 | * console.log(pattern.test("https://blog.example.com/article/123")); // false 85 | * ``` 86 | */ 87 | export class URLPattern { 88 | [webidl.brand]: symbol; 89 | [_components]: any; 90 | 91 | constructor(input: URLPatternInput, baseURL: string | undefined = undefined) { 92 | this[webidl.brand] = webidl.brand; 93 | const prefix = "Failed to construct 'URLPattern'"; 94 | webidl.requiredArguments(arguments.length, 1, { prefix }); 95 | input = webidl.converters.URLPatternInput(input, { 96 | prefix, 97 | context: "Argument 1", 98 | }); 99 | if (baseURL !== undefined) { 100 | baseURL = webidl.converters.USVString(baseURL, { 101 | prefix, 102 | context: "Argument 2", 103 | }); 104 | } 105 | 106 | const components = urlpatternParse(input, baseURL); 107 | 108 | for (const key of Object.keys(components)) { 109 | try { 110 | components[key].regexp = new RegExp( 111 | components[key].regexpString, 112 | "u", 113 | ); 114 | } catch (e) { 115 | throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`); 116 | } 117 | } 118 | 119 | this[_components] = components; 120 | } 121 | 122 | /** The pattern string for the `protocol`. */ 123 | get protocol() { 124 | webidl.assertBranded(this, URLPattern); 125 | return this[_components].protocol.patternString; 126 | } 127 | 128 | /** The pattern string for the `username`. */ 129 | get username() { 130 | webidl.assertBranded(this, URLPattern); 131 | return this[_components].username.patternString; 132 | } 133 | 134 | /** The pattern string for the `password`. */ 135 | get password() { 136 | webidl.assertBranded(this, URLPattern); 137 | return this[_components].password.patternString; 138 | } 139 | 140 | /** The pattern string for the `hostname`. */ 141 | get hostname() { 142 | webidl.assertBranded(this, URLPattern); 143 | return this[_components].hostname.patternString; 144 | } 145 | 146 | /** The pattern string for the `port`. */ 147 | get port() { 148 | webidl.assertBranded(this, URLPattern); 149 | return this[_components].port.patternString; 150 | } 151 | 152 | /** The pattern string for the `pathname`. */ 153 | get pathname() { 154 | webidl.assertBranded(this, URLPattern); 155 | return this[_components].pathname.patternString; 156 | } 157 | 158 | /** The pattern string for the `search`. */ 159 | get search() { 160 | webidl.assertBranded(this, URLPattern); 161 | return this[_components].search.patternString; 162 | } 163 | 164 | /** The pattern string for the `hash`. */ 165 | get hash() { 166 | webidl.assertBranded(this, URLPattern); 167 | return this[_components].hash.patternString; 168 | } 169 | 170 | /** 171 | * Test if the given input matches the stored pattern. 172 | * 173 | * The input can either be provided as a url string (with an optional base), 174 | * or as individual components in the form of an object. 175 | * 176 | * ```ts 177 | * const pattern = new URLPattern("https://example.com/books/:id"); 178 | * 179 | * // Test a url string. 180 | * console.log(pattern.test("https://example.com/books/123")); // true 181 | * 182 | * // Test a relative url with a base. 183 | * console.log(pattern.test("/books/123", "https://example.com")); // true 184 | * 185 | * // Test an object of url components. 186 | * console.log(pattern.test({ pathname: "/books/123" })); // true 187 | * ``` 188 | */ 189 | test( 190 | input: URLPatternInput, 191 | baseURL: string | undefined = undefined, 192 | ): boolean { 193 | webidl.assertBranded(this, URLPattern); 194 | const prefix = "Failed to execute 'test' on 'URLPattern'"; 195 | webidl.requiredArguments(arguments.length, 1, { prefix }); 196 | input = webidl.converters.URLPatternInput(input, { 197 | prefix, 198 | context: "Argument 1", 199 | }); 200 | if (baseURL !== undefined) { 201 | baseURL = webidl.converters.USVString(baseURL, { 202 | prefix, 203 | context: "Argument 2", 204 | }); 205 | } 206 | 207 | const res = urlpatternProcessMatchInput( 208 | input, 209 | baseURL, 210 | ); 211 | if (res === null) { 212 | return false; 213 | } 214 | 215 | const [values] = res; 216 | 217 | for (const key of Object.keys(values)) { 218 | if (!this[_components][key].regexp.test(values[key])) { 219 | return false; 220 | } 221 | } 222 | 223 | return true; 224 | } 225 | 226 | /** 227 | * Match the given input against the stored pattern. 228 | * 229 | * The input can either be provided as a url string (with an optional base), 230 | * or as individual components in the form of an object. 231 | * 232 | * ```ts 233 | * const pattern = new URLPattern("https://example.com/books/:id"); 234 | * 235 | * // Match a url string. 236 | * let match = pattern.match("https://example.com/books/123"); 237 | * console.log(match.pathname.groups.id); // 123 238 | * 239 | * // Match a relative url with a base. 240 | * match = pattern.match("/books/123", "https://example.com"); 241 | * console.log(match.pathname.groups.id); // 123 242 | * 243 | * // Match an object of url components. 244 | * match = pattern.match({ pathname: "/books/123" }); 245 | * console.log(match.pathname.groups.id); // 123 246 | * ``` 247 | */ 248 | exec( 249 | input: URLPatternInput, 250 | baseURL: string | undefined = undefined, 251 | ): URLPatternResult | null { 252 | webidl.assertBranded(this, URLPattern); 253 | const prefix = "Failed to execute 'exec' on 'URLPattern'"; 254 | webidl.requiredArguments(arguments.length, 1, { prefix }); 255 | input = webidl.converters.URLPatternInput(input, { 256 | prefix, 257 | context: "Argument 1", 258 | }); 259 | if (baseURL !== undefined) { 260 | baseURL = webidl.converters.USVString(baseURL, { 261 | prefix, 262 | context: "Argument 2", 263 | }); 264 | } 265 | 266 | const res = urlpatternProcessMatchInput( 267 | input, 268 | baseURL, 269 | ); 270 | if (res === null) { 271 | return null; 272 | } 273 | 274 | const [values, inputs] = res; 275 | if (inputs[1] === null) { 276 | inputs.pop(); 277 | } 278 | 279 | const result: any = { inputs }; 280 | 281 | for (const key of Object.keys(values)) { 282 | const component = this[_components][key]; 283 | const input = values[key]; 284 | const match = component.regexp.exec(input); 285 | if (match === null) { 286 | return null; 287 | } 288 | const groupEntries = component.groupNameList.map( 289 | (name: string, i: number) => [name, match[i + 1] ?? ""], 290 | ); 291 | const groups = Object.fromEntries(groupEntries); 292 | result[key] = { 293 | input, 294 | groups, 295 | }; 296 | } 297 | 298 | return result; 299 | } 300 | 301 | [Symbol.for("Deno.customInspect")](inspect: (a: unknown) => string) { 302 | return `URLPattern ${ 303 | inspect({ 304 | protocol: this.protocol, 305 | username: this.username, 306 | password: this.password, 307 | hostname: this.hostname, 308 | port: this.port, 309 | pathname: this.pathname, 310 | search: this.search, 311 | hash: this.hash, 312 | }) 313 | }`; 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | tab_spaces = 2 3 | edition = "2021" 4 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { encode } from "https://deno.land/std@0.120.0/encoding/base64.ts"; 2 | import { compress } from "https://deno.land/x/lz4@v0.1.2/mod.ts"; 3 | import { minify } from "https://jspm.dev/terser@5.10.0"; 4 | 5 | const name = "urlpattern"; 6 | 7 | await Deno.run({ 8 | cmd: ["wasm-pack", "build", "--target", "web", "--release"], 9 | }).status(); 10 | 11 | const wasm = await Deno.readFile(`./pkg/${name}_bg.wasm`); 12 | const init = await Deno.readTextFile(`./pkg/${name}.js`); 13 | const encoded = encode(compress(wasm)); 14 | const source = 15 | `import { decode } from "https://deno.land/std@0.120.0/encoding/base64.ts"; 16 | import { decompress } from "https://deno.land/x/lz4@v0.1.2/mod.ts"; 17 | export const source = decompress(decode("${encoded}")); 18 | ${init}`; 19 | const output = await minify(source, { 20 | mangle: { module: true }, 21 | output: { preamble: "// deno-fmt-ignore-file\n// deno-lint-ignore-file" }, 22 | }); 23 | 24 | await Deno.writeTextFile("wasm/wasm.js", output.code); 25 | -------------------------------------------------------------------------------- /wasm/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | #[wasm_bindgen] 4 | pub fn urlpattern_parse( 5 | input: JsValue, 6 | base_url: Option, 7 | ) -> Result { 8 | let input = input.into_serde().unwrap(); 9 | let init = urlpattern::quirks::process_construct_pattern_input( 10 | input, 11 | base_url.as_deref(), 12 | ) 13 | .map_err(|e| JsValue::from_str(&e.to_string()))?; 14 | 15 | let pattern = 16 | urlpattern::quirks::parse_pattern(init).map_err(|e| e.to_string())?; 17 | 18 | Ok(JsValue::from_serde(&pattern).unwrap()) 19 | } 20 | 21 | #[wasm_bindgen] 22 | pub fn urlpattern_process_match_input( 23 | input: JsValue, 24 | base_url: Option, 25 | ) -> Result { 26 | let input = input.into_serde().unwrap(); 27 | let res = urlpattern::quirks::process_match_input(input, base_url.as_deref()) 28 | .map_err(|e| JsValue::from_str(&e.to_string()))?; 29 | 30 | let (input, inputs) = match res { 31 | Some((input, inputs)) => (input, inputs), 32 | None => return Ok(JsValue::NULL), 33 | }; 34 | 35 | Ok( 36 | JsValue::from_serde( 37 | &urlpattern::quirks::parse_match_input(input) 38 | .map(|input| (input, inputs)), 39 | ) 40 | .unwrap(), 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /webidl.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. 2 | // Copyright 2021 the denosaurs team. All rights reserved. MIT license. 3 | // deno-lint-ignore-file 4 | 5 | export const brand = Symbol("[[webidl.brand]]"); 6 | 7 | export function assertBranded(self, prototype) { 8 | if (!(self instanceof prototype) || self[brand] !== brand) { 9 | throw new TypeError("Illegal invocation"); 10 | } 11 | } 12 | 13 | export function requiredArguments(length, required, opts = {}) { 14 | if (length < required) { 15 | const errMsg = `${ 16 | opts.prefix ? opts.prefix + ": " : "" 17 | }${required} argument${ 18 | required === 1 ? "" : "s" 19 | } required, but only ${length} present.`; 20 | throw new TypeError(errMsg); 21 | } 22 | } 23 | 24 | export function createDictionaryConverter(name, ...dictionaries) { 25 | let hasRequiredKey = false; 26 | const allMembers = []; 27 | for (const members of dictionaries) { 28 | for (const member of members) { 29 | if (member.required) { 30 | hasRequiredKey = true; 31 | } 32 | allMembers.push(member); 33 | } 34 | } 35 | allMembers.sort((a, b) => { 36 | if (a.key == b.key) { 37 | return 0; 38 | } 39 | return a.key < b.key ? -1 : 1; 40 | }); 41 | 42 | const defaultValues = {}; 43 | for (const member of allMembers) { 44 | if ("defaultValue" in member) { 45 | const idlMemberValue = member.defaultValue; 46 | const imvType = typeof idlMemberValue; 47 | // Copy by value types can be directly assigned, copy by reference types 48 | // need to be re-created for each allocation. 49 | if ( 50 | imvType === "number" || imvType === "boolean" || 51 | imvType === "string" || imvType === "bigint" || 52 | imvType === "undefined" 53 | ) { 54 | defaultValues[member.key] = member.converter(idlMemberValue, {}); 55 | } else { 56 | Object.defineProperty(defaultValues, member.key, { 57 | get() { 58 | return member.converter(idlMemberValue, member.defaultValue); 59 | }, 60 | enumerable: true, 61 | }); 62 | } 63 | } 64 | } 65 | 66 | return function (V, opts = {}) { 67 | const typeV = type(V); 68 | switch (typeV) { 69 | case "Undefined": 70 | case "Null": 71 | case "Object": 72 | break; 73 | default: 74 | throw makeException( 75 | TypeError, 76 | "can not be converted to a dictionary", 77 | opts, 78 | ); 79 | } 80 | const esDict = V; 81 | 82 | const idlDict = { ...defaultValues }; 83 | 84 | // NOTE: fast path Null and Undefined. 85 | if ((V === undefined || V === null) && !hasRequiredKey) { 86 | return idlDict; 87 | } 88 | 89 | for (const member of allMembers) { 90 | const key = member.key; 91 | 92 | let esMemberValue; 93 | if (typeV === "Undefined" || typeV === "Null") { 94 | esMemberValue = undefined; 95 | } else { 96 | esMemberValue = esDict[key]; 97 | } 98 | 99 | if (esMemberValue !== undefined) { 100 | const context = `'${key}' of '${name}'${ 101 | opts.context ? ` (${opts.context})` : "" 102 | }`; 103 | const converter = member.converter; 104 | const idlMemberValue = converter(esMemberValue, { ...opts, context }); 105 | idlDict[key] = idlMemberValue; 106 | } else if (member.required) { 107 | throw makeException( 108 | TypeError, 109 | `can not be converted to '${name}' because '${key}' is required in '${name}'.`, 110 | { ...opts }, 111 | ); 112 | } 113 | } 114 | 115 | return idlDict; 116 | }; 117 | } 118 | 119 | export const converters = {}; 120 | 121 | converters["DOMString"] = (V, opts = {}) => { 122 | if (opts.treatNullAsEmptyString && V === null) { 123 | return ""; 124 | } 125 | 126 | if (typeof V === "symbol") { 127 | throw makeException( 128 | TypeError, 129 | "is a symbol, which cannot be converted to a string", 130 | opts, 131 | ); 132 | } 133 | 134 | return String(V); 135 | }; 136 | 137 | converters["USVString"] = (V, opts) => { 138 | const S = converters.DOMString(V, opts); 139 | const n = S.length; 140 | let U = ""; 141 | for (let i = 0; i < n; ++i) { 142 | const c = S.charCodeAt(i); 143 | if (c < 0xd800 || c > 0xdfff) { 144 | U += String.fromCodePoint(c); 145 | } else if (0xdc00 <= c && c <= 0xdfff) { 146 | U += String.fromCodePoint(0xfffd); 147 | } else if (i === n - 1) { 148 | U += String.fromCodePoint(0xfffd); 149 | } else { 150 | const d = S.charCodeAt(i + 1); 151 | if (0xdc00 <= d && d <= 0xdfff) { 152 | const a = c & 0x3ff; 153 | const b = d & 0x3ff; 154 | U += String.fromCodePoint((2 << 15) + (2 << 9) * a + b); 155 | ++i; 156 | } else { 157 | U += String.fromCodePoint(0xfffd); 158 | } 159 | } 160 | } 161 | return U; 162 | }; 163 | 164 | converters["URLPatternInput"] = (V, opts) => { 165 | if (typeof V == "object") { 166 | return converters.URLPatternInit(V, opts); 167 | } 168 | return converters.USVString(V, opts); 169 | }; 170 | 171 | converters["URLPatternInit"] = createDictionaryConverter("URLPatternInit", [ 172 | { key: "protocol", converter: converters.USVString }, 173 | { key: "username", converter: converters.USVString }, 174 | { key: "password", converter: converters.USVString }, 175 | { key: "hostname", converter: converters.USVString }, 176 | { key: "port", converter: converters.USVString }, 177 | { key: "pathname", converter: converters.USVString }, 178 | { key: "search", converter: converters.USVString }, 179 | { key: "hash", converter: converters.USVString }, 180 | { key: "baseURL", converter: converters.USVString }, 181 | ]); 182 | --------------------------------------------------------------------------------