Compare commits
No commits in common. "trunk" and "main" have entirely different histories.
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,6 +1,3 @@
|
||||||
**/build/*
|
**/build/*
|
||||||
**/target/*
|
**/target/*
|
||||||
pritty/outputs/*
|
pritty/outputs/*
|
||||||
|
|
||||||
**/*.db
|
|
||||||
*.db
|
|
||||||
|
|
|
||||||
22
deno.jsonc
22
deno.jsonc
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"checkJs": true,
|
|
||||||
"lib": [
|
|
||||||
"esnext",
|
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"deno.window",
|
|
||||||
"deno.unstable"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"fmt": {
|
|
||||||
"semiColons": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"useTabs": true,
|
|
||||||
"lineWidth": 90
|
|
||||||
},
|
|
||||||
"imports": {
|
|
||||||
"@deno/emit": "jsr:@deno/emit@^0.46.0",
|
|
||||||
"htmx.org": "npm:htmx.org@2.0.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
68
deno.lock
68
deno.lock
|
|
@ -1,68 +0,0 @@
|
||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"specifiers": {
|
|
||||||
"jsr:@deno/cache-dir@0.13.2": "0.13.2",
|
|
||||||
"jsr:@deno/emit@0.46": "0.46.0",
|
|
||||||
"jsr:@std/assert@0.223": "0.223.0",
|
|
||||||
"jsr:@std/bytes@0.223": "0.223.0",
|
|
||||||
"jsr:@std/fmt@0.223": "0.223.0",
|
|
||||||
"jsr:@std/fs@0.223": "0.223.0",
|
|
||||||
"jsr:@std/io@0.223": "0.223.0",
|
|
||||||
"jsr:@std/path@0.223": "0.223.0",
|
|
||||||
"npm:htmx.org@2.0.4": "2.0.4"
|
|
||||||
},
|
|
||||||
"jsr": {
|
|
||||||
"@deno/cache-dir@0.13.2": {
|
|
||||||
"integrity": "c22419dfe27ab85f345bee487aaaadba498b005cce3644e9d2528db035c5454d",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/fmt",
|
|
||||||
"jsr:@std/fs",
|
|
||||||
"jsr:@std/io",
|
|
||||||
"jsr:@std/path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@deno/emit@0.46.0": {
|
|
||||||
"integrity": "e276be2c77bac1b93caf775762e2a49a54cb00da2d48ca2b01ed8d7cba9d082c",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@deno/cache-dir",
|
|
||||||
"jsr:@std/path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/assert@0.223.0": {
|
|
||||||
"integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24"
|
|
||||||
},
|
|
||||||
"@std/bytes@0.223.0": {
|
|
||||||
"integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8"
|
|
||||||
},
|
|
||||||
"@std/fmt@0.223.0": {
|
|
||||||
"integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208"
|
|
||||||
},
|
|
||||||
"@std/fs@0.223.0": {
|
|
||||||
"integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c"
|
|
||||||
},
|
|
||||||
"@std/io@0.223.0": {
|
|
||||||
"integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert",
|
|
||||||
"jsr:@std/bytes"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/path@0.223.0": {
|
|
||||||
"integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm": {
|
|
||||||
"htmx.org@2.0.4": {
|
|
||||||
"integrity": "sha512-HLxMCdfXDOJirs3vBZl/ZLoY+c7PfM4Ahr2Ad4YXh6d22T5ltbTXFFkpx9Tgb2vvmWFMbIc3LqN2ToNkZJvyYQ=="
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"workspace": {
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@deno/emit@0.46",
|
|
||||||
"npm:htmx.org@2.0.4"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
26
flake.lock
26
flake.lock
|
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"crane": {
|
"crane": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1744386647,
|
"lastModified": 1734541973,
|
||||||
"narHash": "sha256-DXwQEJllxpYeVOiSlBhQuGjfvkoGHTtILLYO2FvcyzQ=",
|
"narHash": "sha256-1wIgLmhvtfxbJVnhFHUYhPqL3gpLn5JhiS4maaD9RRk=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "d02c1cdd7ec539699aa44e6ff912e15535969803",
|
"rev": "fdd502f921936105869eba53db6593fc2a424c16",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -41,11 +41,11 @@
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1744231114,
|
"lastModified": 1734676450,
|
||||||
"narHash": "sha256-60gLl2rJFt6SRwqWimsTAeHgfsIE1iV0zChdJFOvx8w=",
|
"narHash": "sha256-iwcxhTVe4h5TqW0HsNiOQP27eMBmbBshF+q2UjEy5aU=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "0ccfe532b1433da8e5a23cd513ff6847e0f6a8c2",
|
"rev": "46e19fa0eb3260b2c3ee5b2cf89e73343c1296ab",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -92,10 +92,10 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1741048562,
|
"lastModified": 1733550349,
|
||||||
"narHash": "sha256-W4YZ3fvWZiFYYyd900kh8P8wU6DHSiwaH0j4+fai1Sk=",
|
"narHash": "sha256-NcGumB4Lr6KSDq+nIqXtNA8QwAQKDSZT7N9OTGWbTrs=",
|
||||||
"path": "/nix/store/cdjqlnn7kx4hfmxkry9yjfdvqp2pradh-source",
|
"path": "/nix/store/sqmn1ky3k66661h32djyjvsr8l99330z-source",
|
||||||
"rev": "6af28b834daca767a7ef99f8a7defa957d0ade6f",
|
"rev": "e2605d0744c2417b09f8bf850dfca42fcf537d34",
|
||||||
"type": "path"
|
"type": "path"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -115,11 +115,11 @@
|
||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1742296961,
|
"lastModified": 1734622712,
|
||||||
"narHash": "sha256-gCpvEQOrugHWLimD1wTFOJHagnSEP6VYBDspq96Idu0=",
|
"narHash": "sha256-2Oc2LbFypF1EG3zTVIVcuT5XFJ7R3oAwu2tS8B0qQ0I=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "15d87419f1a123d8f888d608129c3ce3ff8f13d4",
|
"rev": "fe027d79d22f2a7645da4143f5cc0f5f56239b97",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
273
llmc/Cargo.lock
generated
273
llmc/Cargo.lock
generated
|
|
@ -1,273 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "1.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atelier"
|
|
||||||
version = "0.0.0"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bstr"
|
|
||||||
version = "1.11.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-deque"
|
|
||||||
version = "0.8.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-epoch",
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-epoch"
|
|
||||||
version = "0.9.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-utils"
|
|
||||||
version = "0.8.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "globset"
|
|
||||||
version = "0.4.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"bstr",
|
|
||||||
"log",
|
|
||||||
"regex-automata",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ignore"
|
|
||||||
version = "0.4.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-deque",
|
|
||||||
"globset",
|
|
||||||
"log",
|
|
||||||
"memchr",
|
|
||||||
"regex-automata",
|
|
||||||
"same-file",
|
|
||||||
"walkdir",
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "llmc"
|
|
||||||
version = "0.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"atelier",
|
|
||||||
"ignore",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.25"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.93"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.38"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.4.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.8.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "same-file"
|
|
||||||
version = "1.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.217"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.217"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.98"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "walkdir"
|
|
||||||
version = "2.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
|
||||||
dependencies = [
|
|
||||||
"same-file",
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-util"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.59.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_gnullvm",
|
|
||||||
"windows_aarch64_msvc",
|
|
||||||
"windows_i686_gnu",
|
|
||||||
"windows_i686_gnullvm",
|
|
||||||
"windows_i686_msvc",
|
|
||||||
"windows_x86_64_gnu",
|
|
||||||
"windows_x86_64_gnullvm",
|
|
||||||
"windows_x86_64_msvc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "llmc"
|
|
||||||
version = "0.0.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
atelier = { path = "../rs" }
|
|
||||||
ignore = "0.4.23"
|
|
||||||
103
llmc/src/main.rs
103
llmc/src/main.rs
|
|
@ -1,103 +0,0 @@
|
||||||
use atelier::preludes::fs::*;
|
|
||||||
use atelier::preludes::io::*;
|
|
||||||
|
|
||||||
static USAGE: &str = r#"
|
|
||||||
llmc - Collect code in a directory into a single text output, for feeding into an LLM
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
llmc [path] [options]...
|
|
||||||
|
|
||||||
OPTIONS:
|
|
||||||
-i, --input-exts <ext,...> The file extensions to include in the output
|
|
||||||
"#;
|
|
||||||
|
|
||||||
fn find_git_root(start: impl AsRef<Path>) -> IoResult<Option<PathBuf>> {
|
|
||||||
let mut current = Some(start.as_ref().to_path_buf().canonicalize()?);
|
|
||||||
while let Some(c) = current {
|
|
||||||
if c.join(".git").exists() {
|
|
||||||
return Ok(Some(c.to_path_buf().canonicalize()?));
|
|
||||||
} else {
|
|
||||||
current = c.parent().map(|o| o.to_path_buf());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_dir(path: impl AsRef<Path>, included_exts: &[&str]) -> IoResult<()> {
|
|
||||||
let mut types = ignore::types::TypesBuilder::new();
|
|
||||||
for ext in included_exts.iter() {
|
|
||||||
types.add(&ext[1..], &format!("*{ext}")).unwrap();
|
|
||||||
}
|
|
||||||
types.select("all");
|
|
||||||
let types = types.build().unwrap();
|
|
||||||
|
|
||||||
let mut builder = if let Some(root) = find_git_root(path.as_ref())? {
|
|
||||||
ignore::WalkBuilder::new(root)
|
|
||||||
} else {
|
|
||||||
ignore::WalkBuilder::new(path)
|
|
||||||
};
|
|
||||||
|
|
||||||
builder.types(types);
|
|
||||||
|
|
||||||
let walk = builder.build();
|
|
||||||
|
|
||||||
for result in walk {
|
|
||||||
let entry = result.unwrap();
|
|
||||||
let ft = entry.file_type().unwrap();
|
|
||||||
if ft.is_dir() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = entry.path().as_os_str().to_string_lossy();
|
|
||||||
println!("##### Contents of {} #####", path);
|
|
||||||
println!("{}", std::fs::read_to_string(path.as_ref())?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
|
||||||
let mut included_exts = vec![];
|
|
||||||
|
|
||||||
let args: Vec<_> = std::env::args().skip(1).collect();
|
|
||||||
let mut args = args.iter().map(|s| s.as_str());
|
|
||||||
let mut params = vec![];
|
|
||||||
let mut fail = false;
|
|
||||||
|
|
||||||
while !fail {
|
|
||||||
let arg = match args.next() {
|
|
||||||
None => break,
|
|
||||||
Some(v) => v,
|
|
||||||
};
|
|
||||||
|
|
||||||
if arg.starts_with('-') {
|
|
||||||
match arg {
|
|
||||||
"-h" | "--help" => {
|
|
||||||
println!("{USAGE}");
|
|
||||||
return Ok(());
|
|
||||||
},
|
|
||||||
"-i" | "--include-exts" => {
|
|
||||||
fail = args
|
|
||||||
.next()
|
|
||||||
.map(|a| included_exts.extend(a.split(',')))
|
|
||||||
.is_none()
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
params.push(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fail {
|
|
||||||
eprintln!("{USAGE}");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let dir = params.get(0).copied().unwrap_or(".");
|
|
||||||
|
|
||||||
walk_dir(dir, &included_exts)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
402
porthole/Cargo.lock
generated
402
porthole/Cargo.lock
generated
|
|
@ -1,402 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayref"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayvec"
|
|
||||||
version = "0.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.21.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "2.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "blake2b_simd"
|
|
||||||
version = "1.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99"
|
|
||||||
dependencies = [
|
|
||||||
"arrayref",
|
|
||||||
"arrayvec",
|
|
||||||
"constant_time_eq",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "constant_time_eq"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"r-efi",
|
|
||||||
"wasi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.15.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indexmap"
|
|
||||||
version = "2.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
|
||||||
dependencies = [
|
|
||||||
"equivalent",
|
|
||||||
"hashbrown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.171"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "porthole"
|
|
||||||
version = "0.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"rand",
|
|
||||||
"rpassword",
|
|
||||||
"rust-argon2",
|
|
||||||
"serde",
|
|
||||||
"toml",
|
|
||||||
"xdg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ppv-lite86"
|
|
||||||
version = "0.2.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
|
||||||
dependencies = [
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.94"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.40"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "r-efi"
|
|
||||||
version = "5.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
|
||||||
dependencies = [
|
|
||||||
"rand_chacha",
|
|
||||||
"rand_core",
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
|
||||||
dependencies = [
|
|
||||||
"ppv-lite86",
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.9.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rpassword"
|
|
||||||
version = "7.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"rtoolbox",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rtoolbox"
|
|
||||||
version = "0.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rust-argon2"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9d9848531d60c9cbbcf9d166c885316c24bc0e2a9d3eba0956bb6cbbd79bc6e8"
|
|
||||||
dependencies = [
|
|
||||||
"base64",
|
|
||||||
"blake2b_simd",
|
|
||||||
"constant_time_eq",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.219"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.219"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_spanned"
|
|
||||||
version = "0.6.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.8.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"toml_edit",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_datetime"
|
|
||||||
version = "0.6.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_edit"
|
|
||||||
version = "0.22.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"winnow",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasi"
|
|
||||||
version = "0.14.2+wasi-0.2.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
|
||||||
dependencies = [
|
|
||||||
"wit-bindgen-rt",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.48.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.48.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_gnullvm",
|
|
||||||
"windows_aarch64_msvc",
|
|
||||||
"windows_i686_gnu",
|
|
||||||
"windows_i686_msvc",
|
|
||||||
"windows_x86_64_gnu",
|
|
||||||
"windows_x86_64_gnullvm",
|
|
||||||
"windows_x86_64_msvc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.48.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.48.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.48.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.48.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.48.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.48.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.48.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winnow"
|
|
||||||
version = "0.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen-rt"
|
|
||||||
version = "0.39.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xdg"
|
|
||||||
version = "2.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy"
|
|
||||||
version = "0.8.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
|
|
||||||
dependencies = [
|
|
||||||
"zerocopy-derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy-derive"
|
|
||||||
version = "0.8.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "porthole"
|
|
||||||
version = "0.0.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
rpassword = "7.3.1"
|
|
||||||
xdg = "2.5.2"
|
|
||||||
rust-argon2 = "2.1"
|
|
||||||
rand = "0.9.0"
|
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
|
||||||
toml = "0.8.20"
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
# porthole
|
|
||||||
`porthole` is a simple application to expose services running on your localhost
|
|
||||||
via some other host.
|
|
||||||
|
|
||||||
**NOTE**: Currently, only TCP-based services are supported.
|
|
||||||
|
|
||||||
## Quickstart
|
|
||||||
To use `porthole`, you need to have the server running on a host that is exposed
|
|
||||||
to the WWW.
|
|
||||||
|
|
||||||
### Server
|
|
||||||
To configure the server, run `porthole server configure`. This will prompt you
|
|
||||||
for the configuration values you need.
|
|
||||||
|
|
||||||
Once configured, you can run `porthole server start` to launch the server.
|
|
||||||
|
|
||||||
### Client
|
|
||||||
To configure the client, run `porthole client configure`. This will prompt you
|
|
||||||
for the configuration values you need.
|
|
||||||
|
|
||||||
Once configured, you can run `porthole client start <local_port> [remote_port]`.
|
|
||||||
If everything goes well, this will print a `<hostname>:<port>` that should be
|
|
||||||
publically accessible!
|
|
||||||
|
|
||||||
**NOTE**: `[remote_port]` above is optional. If omitted, a random port will be
|
|
||||||
assigned.
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
pub mod prelude;
|
|
||||||
pub mod server;
|
|
||||||
pub mod utils;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args: Vec<_> = std::env::args().skip(1).collect();
|
|
||||||
let args: Vec<_> = args.iter().map(String::as_str).collect();
|
|
||||||
|
|
||||||
match args.as_slice() {
|
|
||||||
&["server", "configure"] => server::configure(),
|
|
||||||
_ => {
|
|
||||||
println!("porthole <command>");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
pub use crate::{server, utils};
|
|
||||||
pub use core::fmt::Debug;
|
|
||||||
pub use core::str::FromStr;
|
|
||||||
pub use rand::prelude::*;
|
|
||||||
pub use serde::{Deserialize, Serialize};
|
|
||||||
pub use std::io::{BufRead, Read, Result as IoResult, Write};
|
|
||||||
pub use std::path::PathBuf;
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct Config {
|
|
||||||
password_hash: String,
|
|
||||||
password_salt: String,
|
|
||||||
port: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure() {
|
|
||||||
let port = utils::prompt("Port: ").unwrap();
|
|
||||||
|
|
||||||
let password = utils::read_matching_passwords().unwrap();
|
|
||||||
let (password_hash, password_salt) = utils::hash_password(&password);
|
|
||||||
|
|
||||||
let config = Config {
|
|
||||||
password_hash,
|
|
||||||
password_salt,
|
|
||||||
port,
|
|
||||||
};
|
|
||||||
|
|
||||||
let out_path = utils::get_config_file_path("server.toml").unwrap();
|
|
||||||
let data = toml::to_string_pretty(&config).unwrap();
|
|
||||||
|
|
||||||
std::fs::write(&out_path, data).unwrap();
|
|
||||||
println!("\nWrote config file to {}", out_path.display());
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub fn get_config_file_path(name: &str) -> IoResult<PathBuf> {
|
|
||||||
let bd = xdg::BaseDirectories::new()?;
|
|
||||||
bd.place_config_file(format!("porthole/{name}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_password(prompt: &str) -> IoResult<String> {
|
|
||||||
let password = rpassword::prompt_password(prompt)?;
|
|
||||||
|
|
||||||
Ok(password)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_matching_passwords() -> IoResult<String> {
|
|
||||||
loop {
|
|
||||||
let password = read_password("Password: ").unwrap();
|
|
||||||
let confirm_password = read_password("Confirm password: ").unwrap();
|
|
||||||
|
|
||||||
if password == confirm_password {
|
|
||||||
return Ok(password);
|
|
||||||
} else {
|
|
||||||
println!("Passwords did not match")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prompt<T: std::str::FromStr>(prompt: &str) -> IoResult<T>
|
|
||||||
where
|
|
||||||
<T as FromStr>::Err: Debug,
|
|
||||||
{
|
|
||||||
let mut stdin = std::io::stdin().lock();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
print!("{}", prompt);
|
|
||||||
std::io::stdout().flush()?;
|
|
||||||
let mut s = String::new();
|
|
||||||
stdin.read_line(&mut s)?;
|
|
||||||
let parsed = match s.trim().parse() {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
println!("{e:?}");
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(parsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn random_string(len: usize) -> String {
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
rand::rng()
|
|
||||||
.sample_iter(&rand::distr::Alphanumeric)
|
|
||||||
.take(len)
|
|
||||||
.map(char::from)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hash_password(password: &str) -> (String /* Hash */, String /* Salt */) {
|
|
||||||
let config = argon2::Config::default();
|
|
||||||
let salt = random_string(16);
|
|
||||||
|
|
||||||
let hash =
|
|
||||||
argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &config)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
(hash, salt)
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
name = "eink"
|
|
||||||
|
|
||||||
[palette]
|
|
||||||
gray-100 = "#d6d6d6"
|
|
||||||
gray-700 = "#474747"
|
|
||||||
gray-800 = "#333333"
|
|
||||||
|
|
||||||
[dark.backgrounds]
|
|
||||||
primary = "@gray-800"
|
|
||||||
|
|
||||||
selection = "@gray-100"
|
|
||||||
status-bar-active = "@gray-100"
|
|
||||||
status-bar-inactive = "@gray-700"
|
|
||||||
cursor = "@gray-100"
|
|
||||||
|
|
||||||
[dark.text]
|
|
||||||
primary = "@gray-100"
|
|
||||||
|
|
||||||
selection = "@gray-800"
|
|
||||||
status-bar-active = "@gray-800"
|
|
||||||
|
|
||||||
[dark.syntax]
|
|
||||||
operator = "@gray-100"
|
|
||||||
import = "@gray-100"
|
|
||||||
function = "@gray-100"
|
|
||||||
constant = "@gray-100"
|
|
||||||
keyword = "@gray-100"
|
|
||||||
string = "@gray-100"
|
|
||||||
identifier = "@gray-100"
|
|
||||||
number = "@gray-100"
|
|
||||||
comment = "@gray-100"
|
|
||||||
macro = "@gray-100"
|
|
||||||
other = "@gray-100"
|
|
||||||
|
|
||||||
[light.backgrounds]
|
|
||||||
primary = "@gray-100"
|
|
||||||
|
|
||||||
selection = "@gray-800"
|
|
||||||
status-bar-active = "@gray-800"
|
|
||||||
status-bar-inactive = "@gray-700"
|
|
||||||
cursor = "@gray-800"
|
|
||||||
|
|
||||||
[light.text]
|
|
||||||
primary = "@gray-800"
|
|
||||||
|
|
||||||
selection = "@gray-100"
|
|
||||||
status-bar-active = "@gray-100"
|
|
||||||
|
|
||||||
[light.syntax]
|
|
||||||
operator = "@gray-800"
|
|
||||||
import = "@gray-800"
|
|
||||||
function = "@gray-800"
|
|
||||||
constant = "@gray-800"
|
|
||||||
keyword = "@gray-800"
|
|
||||||
string = "@gray-800"
|
|
||||||
identifier = "@gray-800"
|
|
||||||
number = "@gray-800"
|
|
||||||
comment = "@gray-800"
|
|
||||||
macro = "@gray-800"
|
|
||||||
other = "@gray-800"
|
|
||||||
|
|
@ -113,29 +113,6 @@ function bindGet(palette: Palette) {
|
||||||
return (entry: TableEntry | undefined) => get(entry, palette);
|
return (entry: TableEntry | undefined) => get(entry, palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateSublime(
|
|
||||||
dir: string,
|
|
||||||
name: string,
|
|
||||||
variantName: string,
|
|
||||||
tables: Tables,
|
|
||||||
palette: Palette,
|
|
||||||
) {
|
|
||||||
let output = '{';
|
|
||||||
const get = bindGet(palette);
|
|
||||||
|
|
||||||
const w = (prop: string, v: string | undefined) => {
|
|
||||||
if (v !== undefined) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
output += `"globals": {`;
|
|
||||||
w('background', get(tables['background']?.['primary']));
|
|
||||||
w('foreground', get(tables['text']?.['primary']));
|
|
||||||
output += `},`;
|
|
||||||
|
|
||||||
output += '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateGhostty(
|
async function generateGhostty(
|
||||||
dir: string,
|
dir: string,
|
||||||
name: string,
|
name: string,
|
||||||
|
|
@ -223,14 +200,12 @@ async function generateEmacs(
|
||||||
background: get(tables['backgrounds']?.['primary']),
|
background: get(tables['backgrounds']?.['primary']),
|
||||||
});
|
});
|
||||||
f('mode-line-active', {
|
f('mode-line-active', {
|
||||||
foreground: get(tables['text']?.['status-bar-active'] ?? tables['text']?.['primary']),
|
foreground: get(tables['text']?.['primary']),
|
||||||
background: get(tables['backgrounds']?.['status-bar-active']),
|
background: get(tables['backgrounds']?.['status-bar-active']),
|
||||||
box: true,
|
box: true,
|
||||||
});
|
});
|
||||||
f('mode-line-inactive', {
|
f('mode-line-inactive', {
|
||||||
foreground: get(
|
foreground: get(tables['text']?.['primary']),
|
||||||
tables['text']?.['status-bar-inactive'] ?? tables['text']?.['primary'],
|
|
||||||
),
|
|
||||||
background: get(tables['backgrounds']?.['status-bar-inactive']),
|
background: get(tables['backgrounds']?.['status-bar-inactive']),
|
||||||
box: true,
|
box: true,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "atelier"
|
|
||||||
version = "0.0.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
resolver = "2"
|
|
||||||
members = [".", "newt"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
rusqlite = { version = "*", optional = true }
|
|
||||||
async-channel = { version = "*", optional = true }
|
|
||||||
urlpattern = { version = "*", optional = true }
|
|
||||||
url = { version = "*", optional = true }
|
|
||||||
|
|
||||||
newt = { path = "./newt", optional = true }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
full = ["web"]
|
|
||||||
web = ["dep:rusqlite", "dep:async-channel", "dep:urlpattern", "dep:url", "dep:newt"]
|
|
||||||
7
rs/newt/Cargo.lock
generated
7
rs/newt/Cargo.lock
generated
|
|
@ -1,7 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "newt"
|
|
||||||
version = "0.0.0"
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "newt"
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
resolver = "2"
|
|
||||||
# members = ["derive", "derive/core"]
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
|
||||||
proc-macro-error = "1.0"
|
|
||||||
proc-macro2 = "1"
|
|
||||||
syn = { version = "2", features = ["full"] }
|
|
||||||
quote = "1"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
# newt-derive = { path = "derive", optional = true }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
# derive = ["dep:newt-derive"]
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
use crate::prelude_internal::*;
|
|
||||||
|
|
||||||
impl Value for () {
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Value for &str {
|
|
||||||
fn render(&self, fmt: &mut dyn Write) -> FmtResult {
|
|
||||||
write!(fmt, "{self}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Value for [(&str, &dyn Value); N] {
|
|
||||||
fn lookup(&self, name: &str) -> Option<&dyn Value> {
|
|
||||||
for (k, v) in self.iter() {
|
|
||||||
if *k == name {
|
|
||||||
return Some(*v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
mod builtin_impls;
|
|
||||||
pub mod parse;
|
|
||||||
pub mod prelude;
|
|
||||||
mod prelude_internal;
|
|
||||||
|
|
||||||
use crate::prelude_internal::*;
|
|
||||||
|
|
||||||
pub trait Value {
|
|
||||||
fn render(&self, w: &mut dyn Write) -> FmtResult {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup(&self, name: &str) -> Option<&dyn Value> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Template {
|
|
||||||
commands: Vec<Command>,
|
|
||||||
}
|
|
||||||
impl Template {
|
|
||||||
pub fn render(&self, env: &dyn Value, target: &mut dyn Write) {
|
|
||||||
for command in self.commands.iter() {
|
|
||||||
match command {
|
|
||||||
Command::Print(s) => {
|
|
||||||
let _ = write!(target, "{s}");
|
|
||||||
},
|
|
||||||
Command::LookupPrint(s) => {
|
|
||||||
if let Some(value) = env.lookup(s) {
|
|
||||||
value.render(target);
|
|
||||||
} else {
|
|
||||||
let _ = write!(target, "<<<'{s}' not found>>>");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Command {
|
|
||||||
Print(String),
|
|
||||||
LookupPrint(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(input: &str) -> Template {
|
|
||||||
let mut state = parse::ParseState::new(input);
|
|
||||||
let mut commands = vec![];
|
|
||||||
|
|
||||||
state.parse_root(&mut commands);
|
|
||||||
|
|
||||||
Template { commands }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(input: &str, env: &dyn Value) -> String {
|
|
||||||
let template = parse(input);
|
|
||||||
let mut output = String::new();
|
|
||||||
|
|
||||||
template.render(env, &mut output);
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! env {
|
|
||||||
($($name:literal : $value:expr),* $(,)?) => {
|
|
||||||
&[$(($name, &$value as &dyn crate::Value),)*]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
macro_rules! go {
|
|
||||||
(($input:expr, $env:expr), $output:expr) => {
|
|
||||||
assert_eq!(crate::render($input, $env), $output)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple() {
|
|
||||||
go!(("Hello, World!", &()), "Hello, World!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn lookup() {
|
|
||||||
go!(
|
|
||||||
("Hello, {name}!", env! { "name": "Steve" }),
|
|
||||||
"Hello, Steve!"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
use crate::prelude_internal::*;
|
|
||||||
|
|
||||||
pub struct ParseState<'a> {
|
|
||||||
input: &'a str,
|
|
||||||
at: usize,
|
|
||||||
}
|
|
||||||
impl<'a> ParseState<'a> {
|
|
||||||
pub fn new(input: &'a str) -> Self {
|
|
||||||
Self { input, at: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.head().is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn head(&self) -> &str {
|
|
||||||
&self.input[self.at..]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn goto(&mut self, at: usize) {
|
|
||||||
self.at = at;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn skip(&mut self, amt: usize) {
|
|
||||||
self.at += amt;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn back(&mut self, amt: usize) {
|
|
||||||
self.at -= amt;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_root(&mut self, out: &mut Vec<Command>) {
|
|
||||||
while !self.is_empty() {
|
|
||||||
let head = self.head();
|
|
||||||
if let Some(bo) = head.find('{') {
|
|
||||||
if bo != 0 {
|
|
||||||
out.push(Command::Print(head[0..bo].to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.goto(bo);
|
|
||||||
self.parse_directive(out);
|
|
||||||
} else {
|
|
||||||
out.push(Command::Print(head.to_string()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_directive(&mut self, out: &mut Vec<Command>) {
|
|
||||||
assert!(self.head().starts_with('{'));
|
|
||||||
|
|
||||||
let mut ok = Err(self.at);
|
|
||||||
|
|
||||||
'inner: {
|
|
||||||
self.skip(1);
|
|
||||||
let head = self.head();
|
|
||||||
if let Some(bo) = head.find('}') {
|
|
||||||
let inner = &head[..bo];
|
|
||||||
let bo = bo + 1;
|
|
||||||
|
|
||||||
let mut inner = inner.split(' ').collect::<Vec<_>>();
|
|
||||||
match inner.as_slice() {
|
|
||||||
&[name] => {
|
|
||||||
out.push(Command::LookupPrint(name.to_string()));
|
|
||||||
ok = Ok(bo);
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match ok {
|
|
||||||
Err(checkpoint) => self.goto(checkpoint),
|
|
||||||
Ok(skip) => self.skip(skip),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
pub use crate::{Command, Template, Value};
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
pub use crate::prelude::*;
|
|
||||||
|
|
||||||
pub use std::fmt::{Display, Formatter, Result as FmtResult, Write};
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
use core::fmt::Debug;
|
|
||||||
use std::borrow::Borrow;
|
|
||||||
|
|
||||||
pub struct VecMap<K, V> {
|
|
||||||
items: Vec<(K, V)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V> Debug for VecMap<K, V>
|
|
||||||
where
|
|
||||||
K: Debug,
|
|
||||||
V: Debug,
|
|
||||||
{
|
|
||||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
||||||
fmt.debug_map()
|
|
||||||
.entries(self.iter().map(|&(ref k, ref v)| (k, v)))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V> Default for VecMap<K, V> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<K, V> VecMap<K, V> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { items: vec![] }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &(K, V)> {
|
|
||||||
self.items.iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V> VecMap<K, V>
|
|
||||||
where
|
|
||||||
K: Eq,
|
|
||||||
{
|
|
||||||
pub fn get_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
|
|
||||||
where
|
|
||||||
K: Borrow<Q>,
|
|
||||||
Q: Eq + ?Sized,
|
|
||||||
{
|
|
||||||
self.items
|
|
||||||
.iter_mut()
|
|
||||||
.find(|(ki, _)| ki.borrow() == k)
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get<Q>(&self, k: &Q) -> Option<&V>
|
|
||||||
where
|
|
||||||
K: Borrow<Q>,
|
|
||||||
Q: Eq + ?Sized,
|
|
||||||
{
|
|
||||||
self.items
|
|
||||||
.iter()
|
|
||||||
.find(|(ki, _)| ki.borrow() == k)
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, k: K, mut v: V) -> Option<V> {
|
|
||||||
if let Some(vi) = self.get_mut(&k) {
|
|
||||||
core::mem::swap(vi, &mut v);
|
|
||||||
|
|
||||||
return Some(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.items.push((k, v));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
pub mod containers;
|
|
||||||
pub mod flags;
|
|
||||||
pub mod prelude;
|
|
||||||
#[cfg(feature = "web")]
|
|
||||||
pub mod router;
|
|
||||||
#[cfg(feature = "web")]
|
|
||||||
pub mod rusqlite_thread_pool;
|
|
||||||
|
|
||||||
pub mod preludes {
|
|
||||||
pub mod fmt {
|
|
||||||
pub use core::fmt::{Display, Formatter, Result as FmtResult};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod io {
|
|
||||||
pub use std::io::Result as IoResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod fs {
|
|
||||||
pub use std::path::{Path, PathBuf};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
pub use core::fmt::{Display, Formatter, Result as FmtResult};
|
|
||||||
101
rs/src/router.rs
101
rs/src/router.rs
|
|
@ -1,101 +0,0 @@
|
||||||
use crate::containers::VecMap;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use url::Url;
|
|
||||||
use urlpattern::{UrlPattern, UrlPatternInit, UrlPatternMatchInput};
|
|
||||||
|
|
||||||
pub struct PathParams(HashMap<String, Option<String>>);
|
|
||||||
impl PathParams {
|
|
||||||
pub fn get(&self, name: &str) -> &str {
|
|
||||||
let s = match self.0.get(name) {
|
|
||||||
None => return "",
|
|
||||||
Some(s) => s,
|
|
||||||
};
|
|
||||||
|
|
||||||
s.as_ref().map(|s| s.as_str()).unwrap_or("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UrlPatternWithInput {
|
|
||||||
pathname: String,
|
|
||||||
pattern: UrlPattern,
|
|
||||||
}
|
|
||||||
impl PartialEq for UrlPatternWithInput {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.pathname == other.pathname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Eq for UrlPatternWithInput {
|
|
||||||
}
|
|
||||||
pub struct Router<T> {
|
|
||||||
patterns: VecMap<UrlPatternWithInput, VecMap<String, T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Router<T> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
patterns: VecMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(&mut self, method: &str, pathname: &str, value: T) {
|
|
||||||
let pathname = pathname.to_string();
|
|
||||||
let method = method.to_string();
|
|
||||||
|
|
||||||
let upwi = UrlPatternWithInput {
|
|
||||||
pathname: pathname.clone(),
|
|
||||||
pattern: UrlPattern::parse(
|
|
||||||
UrlPatternInit {
|
|
||||||
pathname: Some(pathname),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Default::default(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(methods) = self.patterns.get_mut(&upwi) {
|
|
||||||
methods.insert(method, value);
|
|
||||||
} else {
|
|
||||||
let mut methods = VecMap::new();
|
|
||||||
methods.insert(method, value);
|
|
||||||
self.patterns.insert(upwi, methods);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(
|
|
||||||
&self,
|
|
||||||
method: &str,
|
|
||||||
url: &str,
|
|
||||||
) -> Result<(&T, PathParams), u16> {
|
|
||||||
let url = match Url::parse(url) {
|
|
||||||
Ok(u) => u,
|
|
||||||
Err(_) => match Url::parse(&format!("http://example.com{url}")) {
|
|
||||||
Ok(u) => u,
|
|
||||||
Err(_) => return Err(400),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (upwi, methods) in self.patterns.iter() {
|
|
||||||
let pattern = &upwi.pattern;
|
|
||||||
let result = pattern.exec(UrlPatternMatchInput::Url(url.clone()));
|
|
||||||
let path_groups = match result {
|
|
||||||
Ok(Some(res)) => res.pathname.groups,
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(t) = methods.get(method) {
|
|
||||||
return Ok((t, PathParams(path_groups)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(405);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(404)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod methods {
|
|
||||||
pub const GET: &'static str = "GET";
|
|
||||||
pub const POST: &'static str = "POST";
|
|
||||||
pub const PUT: &'static str = "PUT";
|
|
||||||
}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
use std::thread::JoinHandle;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use async_channel::{Receiver, Sender};
|
|
||||||
use rusqlite::Connection;
|
|
||||||
|
|
||||||
pub type RusqliteThreadPoolTask =
|
|
||||||
Box<dyn FnOnce(&mut Connection) -> () + Send + Sync + 'static>;
|
|
||||||
pub type RusqliteThreadPoolWorker =
|
|
||||||
(JoinHandle<()>, Sender<RusqliteThreadPoolTask>);
|
|
||||||
|
|
||||||
pub type PoolReceiver = Receiver<RusqliteThreadPoolTask>;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PoolSender {
|
|
||||||
tx: Sender<RusqliteThreadPoolTask>,
|
|
||||||
}
|
|
||||||
impl PoolSender {
|
|
||||||
pub async fn send<T>(
|
|
||||||
&self,
|
|
||||||
f: impl FnOnce(&mut Connection) -> rusqlite::Result<T>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
) -> rusqlite::Result<T>
|
|
||||||
where
|
|
||||||
T: Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
let (tx, rx) = async_channel::bounded(1);
|
|
||||||
let task = move |conn: &mut Connection| {
|
|
||||||
let out = f(conn);
|
|
||||||
tx.send_blocking(out).unwrap();
|
|
||||||
};
|
|
||||||
let task: RusqliteThreadPoolTask = Box::new(task);
|
|
||||||
self.tx.send(task).await.unwrap();
|
|
||||||
|
|
||||||
rx.recv().await.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_worker(
|
|
||||||
mut conn: Connection,
|
|
||||||
rx: Receiver<RusqliteThreadPoolTask>,
|
|
||||||
) -> JoinHandle<()> {
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
while let Ok(task) = rx.try_recv() {
|
|
||||||
task(&mut conn);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_threadpool(
|
|
||||||
n_workers: u8,
|
|
||||||
db_path: &str,
|
|
||||||
mut init: impl FnMut(&mut Connection) -> rusqlite::Result<()>,
|
|
||||||
) -> rusqlite::Result<(PoolSender, Vec<JoinHandle<()>>, PoolReceiver)> {
|
|
||||||
assert!(n_workers > 0);
|
|
||||||
|
|
||||||
let (tx, rx) = async_channel::unbounded();
|
|
||||||
let mut workers = vec![];
|
|
||||||
for _ in 0..n_workers {
|
|
||||||
let mut conn = Connection::open(db_path)?;
|
|
||||||
init(&mut conn)?;
|
|
||||||
|
|
||||||
let handle = spawn_worker(conn, rx.clone());
|
|
||||||
|
|
||||||
workers.push(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok((PoolSender { tx }, workers, rx));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_threadpool_supervised(
|
|
||||||
n_workers: u8,
|
|
||||||
db_path: &str,
|
|
||||||
mut init: impl FnMut(&mut Connection) -> rusqlite::Result<()>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ Clone
|
|
||||||
+ 'static,
|
|
||||||
) -> rusqlite::Result<(PoolSender, JoinHandle<()>, PoolReceiver)> {
|
|
||||||
let (tx, mut workers, rx) =
|
|
||||||
spawn_threadpool(n_workers, db_path, init.clone())?;
|
|
||||||
|
|
||||||
let rxt = rx.clone();
|
|
||||||
let db_path = db_path.to_string();
|
|
||||||
let supervisor = std::thread::spawn(move || {
|
|
||||||
let rx = rxt;
|
|
||||||
let db_path = db_path;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut dead_worker_idxs = vec![];
|
|
||||||
for (i, worker) in workers.iter().enumerate() {
|
|
||||||
if worker.is_finished() {
|
|
||||||
dead_worker_idxs.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in dead_worker_idxs {
|
|
||||||
let mut conn = Connection::open(&db_path).unwrap();
|
|
||||||
init(&mut conn).unwrap();
|
|
||||||
|
|
||||||
workers[i] = spawn_worker(conn, rx.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_secs(1));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok((tx, supervisor, rx));
|
|
||||||
}
|
|
||||||
|
|
@ -3,4 +3,3 @@ hard_tabs = true
|
||||||
match_block_trailing_comma = true
|
match_block_trailing_comma = true
|
||||||
max_width = 80
|
max_width = 80
|
||||||
empty_item_single_line = false
|
empty_item_single_line = false
|
||||||
format_strings = true
|
|
||||||
|
|
|
||||||
1167
shelves/Cargo.lock
generated
1167
shelves/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,16 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "shelves"
|
|
||||||
version = "0.0.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "shelves"
|
|
||||||
path = "backend/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tokio = { version = "1.43.0", features = ["rt-multi-thread", "fs", "net"] }
|
|
||||||
axum = { version = "0.8.1", features = ["http1", "macros"] }
|
|
||||||
http = "1.2.0"
|
|
||||||
|
|
||||||
atelier = { path = "../rs" }
|
|
||||||
rusqlite = { version = "0.33.0", features = ["bundled"] }
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import { Database } from '@db/sqlite';
|
|
||||||
|
|
||||||
export function migrate(db: Database) {
|
|
||||||
const migrationDirEntries = Deno.readDirSync(import.meta.dirname + '/migrations');
|
|
||||||
const migrations = [];
|
|
||||||
for (const migration of migrationDirEntries) {
|
|
||||||
const content = Deno.readTextFileSync(
|
|
||||||
import.meta.dirname + `/migrations/${migration.name}`,
|
|
||||||
);
|
|
||||||
migrations.push([migration.name, content]);
|
|
||||||
}
|
|
||||||
|
|
||||||
migrations.sort((a, b) => a[0].localeCompare(b[0]));
|
|
||||||
|
|
||||||
const schemaVersionDesired = migrations.length;
|
|
||||||
const { user_version: schemaVersionDb } = db.prepare('pragma user_version').get<
|
|
||||||
{ user_version: number }
|
|
||||||
>()!;
|
|
||||||
|
|
||||||
let applyMigrations = false;
|
|
||||||
if (schemaVersionDb != schemaVersionDesired) {
|
|
||||||
console.error(
|
|
||||||
`The database schema is on ${schemaVersionDb}, but the desired version is ${schemaVersionDesired}`,
|
|
||||||
);
|
|
||||||
applyMigrations = confirm('Apply migrations?');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!applyMigrations) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const migrationsToApply = migrations.slice(schemaVersionDb);
|
|
||||||
const txn = db.transaction(() => {
|
|
||||||
for (const [name, migration] of migrationsToApply) {
|
|
||||||
try {
|
|
||||||
db.exec(migration);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`While running migration ${name}`, { cause: e });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db.exec(`pragma user_version = ${schemaVersionDesired}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
txn.immediate();
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
mod prelude;
|
|
||||||
use prelude::*;
|
|
||||||
mod routes;
|
|
||||||
mod templates;
|
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use axum::handler::HandlerWithoutStateExt;
|
|
||||||
use std::error::Error;
|
|
||||||
use tokio::net::TcpListener;
|
|
||||||
|
|
||||||
fn migrate(connection: &mut rusqlite::Connection) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(connection: &mut rusqlite::Connection) -> rusqlite::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn serve(dbs: DbS, router: Arc<Router>, req: Request) -> Response {
|
|
||||||
let method = req.method().as_str();
|
|
||||||
let uri = format!("{}", req.uri());
|
|
||||||
|
|
||||||
let (handler, path_params) = match router.get(method, &uri) {
|
|
||||||
Ok(h) => h,
|
|
||||||
Err(sc) => {
|
|
||||||
return (http::status::StatusCode::from_u16(sc).unwrap(), "")
|
|
||||||
.into_response()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
handler(req, RequestCtx { dbs, path_params }).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn go() -> Result<()> {
|
|
||||||
let mut conn = rusqlite::Connection::open("./shelves.db")?;
|
|
||||||
migrate(&mut conn)?;
|
|
||||||
|
|
||||||
let (tx, _, _) =
|
|
||||||
atelier::rusqlite_thread_pool::spawn_threadpool_supervised(
|
|
||||||
4,
|
|
||||||
"./shelves.db",
|
|
||||||
|conn| init(conn),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let addr = "127.0.0.1:8333".parse::<SocketAddr>()?;
|
|
||||||
let listener = TcpListener::bind(addr).await?;
|
|
||||||
|
|
||||||
let router = Arc::new(routes::make_router());
|
|
||||||
let s = move |req: Request| {
|
|
||||||
let tx = tx.clone();
|
|
||||||
let router = router.clone();
|
|
||||||
|
|
||||||
serve(tx, router.clone(), req)
|
|
||||||
};
|
|
||||||
|
|
||||||
axum::serve(listener, s.into_make_service()).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
rt.block_on(go()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { Database } from '@db/sqlite';
|
|
||||||
|
|
||||||
import { migrate } from '@/backend/db-schema.ts';
|
|
||||||
import { makeRoutes } from '@/backend/routes.ts';
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const db = new Database('shelves.db');
|
|
||||||
await migrate(db);
|
|
||||||
|
|
||||||
const handler = makeRoutes(db);
|
|
||||||
|
|
||||||
await Deno.serve({ port: 8444 }, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
await main();
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
create table blob (
|
|
||||||
blob_id integer primary key autoincrement,
|
|
||||||
data blob not null,
|
|
||||||
content_type text not null,
|
|
||||||
|
|
||||||
created_timestamp text not null
|
|
||||||
) strict;
|
|
||||||
|
|
||||||
create table shelf (
|
|
||||||
shelf_id integer primary key autoincrement,
|
|
||||||
shelf_xid text not null unique,
|
|
||||||
|
|
||||||
name text not null,
|
|
||||||
|
|
||||||
created_timestamp text not null
|
|
||||||
) strict;
|
|
||||||
|
|
||||||
create table item (
|
|
||||||
item_id integer primary key autoincrement,
|
|
||||||
item_xid text not null unique,
|
|
||||||
|
|
||||||
item_type text not null,
|
|
||||||
name text not null,
|
|
||||||
image_blob_id integer,
|
|
||||||
|
|
||||||
started_timestamp text,
|
|
||||||
finished_timestamp text,
|
|
||||||
|
|
||||||
rating real,
|
|
||||||
review text not null,
|
|
||||||
|
|
||||||
other_metadata_json text,
|
|
||||||
|
|
||||||
foreign key (image_blob_id) references blob(blob_id)
|
|
||||||
) strict;
|
|
||||||
|
|
||||||
create table shelf_item (
|
|
||||||
shelf_item_id integer primary key,
|
|
||||||
|
|
||||||
item_id integer not null,
|
|
||||||
collection_id integer not null,
|
|
||||||
|
|
||||||
foreign key (item_id) references item(item_id),
|
|
||||||
foreign key (collection_id) references collection(collection_id),
|
|
||||||
unique (item_id, collection_id)
|
|
||||||
) strict;
|
|
||||||
|
|
||||||
create table shelf_arrangement (
|
|
||||||
shelf_arrangement_id integer primary key,
|
|
||||||
shelf_arrangement_xid text not null unique,
|
|
||||||
|
|
||||||
name text not null,
|
|
||||||
|
|
||||||
collection_id integer not null,
|
|
||||||
shelf_arrangement_json_blob text not null,
|
|
||||||
|
|
||||||
foreign key (collection_id) references collection(collection_id)
|
|
||||||
) strict;
|
|
||||||
|
|
||||||
create table activity (
|
|
||||||
activity_id integer primary key,
|
|
||||||
|
|
||||||
activity_json_blob text not null
|
|
||||||
) strict;
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
use std::error::Error;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
pub use atelier::rusqlite_thread_pool::PoolSender as DbS;
|
|
||||||
pub type Request = axum::extract::Request;
|
|
||||||
pub struct RequestCtx {
|
|
||||||
pub dbs: DbS,
|
|
||||||
pub path_params: atelier::router::PathParams,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Response = axum::response::Response;
|
|
||||||
pub type Handler = Box<
|
|
||||||
dyn Fn(
|
|
||||||
Request,
|
|
||||||
RequestCtx,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Response> + Send + 'static>>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
>;
|
|
||||||
pub type Router = atelier::router::Router<Handler>;
|
|
||||||
pub type AnyError = Box<dyn Error + Send + Sync + 'static>;
|
|
||||||
pub type Result<T> = core::result::Result<T, AnyError>;
|
|
||||||
|
|
||||||
pub use axum::response::IntoResponse;
|
|
||||||
pub enum HandlerError {
|
|
||||||
InternalServerError(AnyError),
|
|
||||||
}
|
|
||||||
impl<E> From<E> for HandlerError
|
|
||||||
where
|
|
||||||
E: Error + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
fn from(v: E) -> Self {
|
|
||||||
Self::InternalServerError(Box::new(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl IntoResponse for HandlerError {
|
|
||||||
fn into_response(self) -> Response {
|
|
||||||
"".into_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type HandlerResult<T: IntoResponse> = core::result::Result<T, HandlerError>;
|
|
||||||
|
|
||||||
pub use crate::templates::{template_fn, Template, TemplateFn};
|
|
||||||
pub use core::fmt::{Display, Formatter};
|
|
||||||
pub type FmtResult = core::fmt::Result;
|
|
||||||
pub use axum::response::Html;
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
import { Match, Method, Router } from '@atelier/router.ts';
|
|
||||||
import { OrPromise } from '@atelier/util.ts';
|
|
||||||
|
|
||||||
import { Database } from '@db/sqlite';
|
|
||||||
|
|
||||||
import { viewShelf, viewShelves } from '@/backend/routes/shelves.ts';
|
|
||||||
import { postItem, viewCreateItem, viewItem, viewItems } from '@/backend/routes/items.ts';
|
|
||||||
import { viewHome } from '@/backend/routes/home.ts';
|
|
||||||
|
|
||||||
const { GET, POST, PATCH } = Method;
|
|
||||||
|
|
||||||
export type RequestCtx = {
|
|
||||||
pathParams: Record<string, string>;
|
|
||||||
db: Database;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Handler = (req: Request, ctx: RequestCtx) => OrPromise<Response>;
|
|
||||||
|
|
||||||
export function makeRoutes(db: Database) {
|
|
||||||
const router = new Router();
|
|
||||||
const r = (m: Method, p: Match, h: Handler) =>
|
|
||||||
router.register(m, p, (req, pathParams) => {
|
|
||||||
return h(req, { pathParams, db });
|
|
||||||
});
|
|
||||||
|
|
||||||
r(GET, '/', viewHome);
|
|
||||||
r(GET, '/shelves', viewShelves);
|
|
||||||
r(GET, '/shelves/:shelfXid', viewShelf);
|
|
||||||
r(GET, '/items', viewItems);
|
|
||||||
r(GET, '/items/create', viewCreateItem);
|
|
||||||
r(GET, '/items/:itemXid', viewItem);
|
|
||||||
r(POST, '/items/create', postItem);
|
|
||||||
|
|
||||||
r(GET, '/static/js/bundle.js', serveJsBundle);
|
|
||||||
r(GET, '/static/*', serveStatic);
|
|
||||||
|
|
||||||
return router.handle.bind(router);
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXT_TO_MIME = {
|
|
||||||
'.css': 'text/css',
|
|
||||||
};
|
|
||||||
async function serveStatic(req: Request) {
|
|
||||||
const url = new URL(req.url);
|
|
||||||
const path = url.pathname.substring('/static'.length);
|
|
||||||
|
|
||||||
const staticPath = import.meta.dirname + '/../static' + path;
|
|
||||||
const data = await Deno.readFile(staticPath);
|
|
||||||
let mimetype = 'text/plain';
|
|
||||||
for (const [ext, mime] of Object.entries(EXT_TO_MIME)) {
|
|
||||||
if (path.endsWith(ext)) {
|
|
||||||
mimetype = mime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(data, { headers: { 'Content-Type': mimetype } });
|
|
||||||
}
|
|
||||||
|
|
||||||
const isProduction = false;
|
|
||||||
let cachedBundlePath: string | null = null;
|
|
||||||
let cachedBundleAt = new Date(0);
|
|
||||||
async function serveJsBundle(req: Request) {
|
|
||||||
if (isProduction) {
|
|
||||||
throw new Error('todo');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cachedBundlePath === null) {
|
|
||||||
cachedBundlePath = await Deno.makeTempFile({ prefix: 'bundle', suffix: '.js' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const bundlePath = import.meta.dirname + '/../frontend/bundle.ts';
|
|
||||||
const stat = await Deno.stat(bundlePath);
|
|
||||||
const mtime = stat.mtime ?? new Date(0);
|
|
||||||
if (cachedBundleAt > mtime) {
|
|
||||||
const file = await Deno.open(cachedBundlePath);
|
|
||||||
return new Response(file.readable, {
|
|
||||||
headers: { 'Content-Type': 'text/javascript' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const esbuild = await import('npm:esbuild');
|
|
||||||
const { denoPlugins } = await import('jsr:@luca/esbuild-deno-loader');
|
|
||||||
|
|
||||||
const result = await esbuild.build({
|
|
||||||
plugins: [...denoPlugins()],
|
|
||||||
entryPoints: [import.meta.dirname + '/../frontend/bundle.ts'],
|
|
||||||
outfile: cachedBundlePath,
|
|
||||||
bundle: true,
|
|
||||||
format: 'esm',
|
|
||||||
sourcemap: 'inline',
|
|
||||||
});
|
|
||||||
esbuild.stop();
|
|
||||||
|
|
||||||
cachedBundleAt = new Date();
|
|
||||||
|
|
||||||
return new Response((await Deno.open(cachedBundlePath)).readable, {
|
|
||||||
headers: { 'Content-Type': 'text/javascript' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
fn Hello(name: &str) -> impl Template {
|
|
||||||
template_fn(move |f| write!(f, "Hello, {name}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn view(
|
|
||||||
request: Request,
|
|
||||||
ctx: RequestCtx,
|
|
||||||
) -> HandlerResult<impl IntoResponse> {
|
|
||||||
let name = ctx.path_params.get("name");
|
|
||||||
|
|
||||||
let template = Hello(name).display();
|
|
||||||
let output = format!("{template}");
|
|
||||||
|
|
||||||
Ok(Html(output))
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
enum ActivityType {
|
|
||||||
CreatedItem,
|
|
||||||
}
|
|
||||||
struct Activity {
|
|
||||||
activity_type: ActivityType,
|
|
||||||
item_xid: String,
|
|
||||||
created_timestamp: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn view(req: Request, ctx: RequestCtx) -> HandlerResult<String> {
|
|
||||||
let activities: Vec<_> = ctx
|
|
||||||
.dbs
|
|
||||||
.send(|conn| {
|
|
||||||
let mut stmt = conn.prepare(
|
|
||||||
r#"
|
|
||||||
select activity_json_blob from activity
|
|
||||||
order by json_extract(activity_json_blob, '$.created_timestamp')
|
|
||||||
limit 20
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(stmt.query_map([], |r| r.get::<_, String>(0))?.collect())
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
todo!()
|
|
||||||
/*
|
|
||||||
db.prepare(`
|
|
||||||
`).values<[string]>().map(([blob]) => JSON.parse(blob));
|
|
||||||
|
|
||||||
const body = `
|
|
||||||
<h1>Recent Activity</h1>
|
|
||||||
${ActivityList({ activities: recentActivities })}
|
|
||||||
`;
|
|
||||||
return html(View({ title: 'Home', body }));
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
import { View } from '@/backend/templates/index.ts';
|
|
||||||
import { html } from '@atelier/responses.ts';
|
|
||||||
import { arrayIsEmpty } from '@atelier/array.ts';
|
|
||||||
|
|
||||||
type ActivityType = 'created_item';
|
|
||||||
type Activity = {
|
|
||||||
activityType: ActivityType;
|
|
||||||
itemXid: string;
|
|
||||||
createdTimestamp: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Activity(props: { activity: Activity }) {}
|
|
||||||
function ActivityList(props: { activities: Activity[] }) {
|
|
||||||
if (arrayIsEmpty(props.activities)) {
|
|
||||||
return 'No activities yet!';
|
|
||||||
}
|
|
||||||
|
|
||||||
const activities = props.activities.map((activity) => Activity({ activity }));
|
|
||||||
return `
|
|
||||||
<ul>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function viewHome(req: Request, { db }: RequestCtx) {
|
|
||||||
const recentActivities: Activity[] = db.prepare(`
|
|
||||||
select activity_json_blob from activity
|
|
||||||
order by json_extract(activity_json_blob, '$.createdTimestamp')
|
|
||||||
limit 20
|
|
||||||
`).values<[string]>().map(([blob]) => JSON.parse(blob));
|
|
||||||
|
|
||||||
const body = `
|
|
||||||
<h1>Recent Activity</h1>
|
|
||||||
${ActivityList({ activities: recentActivities })}
|
|
||||||
`;
|
|
||||||
return html(View({ title: 'Home', body }));
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import { RequestCtx } from '@/backend/routes.ts';
|
|
||||||
|
|
||||||
import { View } from '@/backend/templates/index.ts';
|
|
||||||
import { html } from '@atelier/responses.ts';
|
|
||||||
import { arrayIsEmpty } from '@atelier/array.ts';
|
|
||||||
|
|
||||||
type ActivityType = 'created_item';
|
|
||||||
type Activity = {
|
|
||||||
activityType: ActivityType;
|
|
||||||
itemXid: string;
|
|
||||||
createdTimestamp: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Activity(props: { activity: Activity }) {}
|
|
||||||
function ActivityList(props: { activities: Activity[] }) {
|
|
||||||
if (arrayIsEmpty(props.activities)) {
|
|
||||||
return 'No activities yet!';
|
|
||||||
}
|
|
||||||
|
|
||||||
const activities = props.activities.map((activity) => Activity({ activity }));
|
|
||||||
return `
|
|
||||||
<ul>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function viewHome(req: Request, { db }: RequestCtx) {
|
|
||||||
const recentActivities: Activity[] = db.prepare(`
|
|
||||||
select activity_json_blob from activity
|
|
||||||
order by json_extract(activity_json_blob, '$.createdTimestamp')
|
|
||||||
limit 20
|
|
||||||
`).values<[string]>().map(([blob]) => JSON.parse(blob));
|
|
||||||
|
|
||||||
const body = `
|
|
||||||
<h1>Recent Activity</h1>
|
|
||||||
${ActivityList({ activities: recentActivities })}
|
|
||||||
`;
|
|
||||||
return html(View({ title: 'Home', body }));
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub async fn view(req: Request, ctx: RequestCtx) -> HandlerResult<String> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn view_one(req: Request, ctx: RequestCtx) -> HandlerResult<String> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn view_create(
|
|
||||||
req: Request,
|
|
||||||
ctx: RequestCtx,
|
|
||||||
) -> HandlerResult<String> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn post_create(
|
|
||||||
req: Request,
|
|
||||||
ctx: RequestCtx,
|
|
||||||
) -> HandlerResult<String> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
import { RequestCtx } from '@/backend/routes.ts';
|
|
||||||
|
|
||||||
import { html } from '@atelier/responses.ts';
|
|
||||||
import { Form, View } from '@/backend/templates/index.ts';
|
|
||||||
|
|
||||||
export function viewItems(req: Request, ctx: RequestCtx) {
|
|
||||||
return new Response('');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function viewItem(req: Request, ctx: RequestCtx) {
|
|
||||||
return new Response('');
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
item_type text not null,
|
|
||||||
name text not null,
|
|
||||||
image_blob_id integer,
|
|
||||||
|
|
||||||
started_timestamp text,
|
|
||||||
finished_timestamp text,
|
|
||||||
|
|
||||||
rating real,
|
|
||||||
review text,
|
|
||||||
|
|
||||||
other_metadata_json text,
|
|
||||||
|
|
||||||
foreign key (image_blob_id) references blob(blob_id)
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function viewCreateItem(req: Request, ctx: RequestCtx) {
|
|
||||||
const body = `
|
|
||||||
${
|
|
||||||
Form({
|
|
||||||
hasFileData: true,
|
|
||||||
action: '/items/create',
|
|
||||||
fields: [
|
|
||||||
{ name: 'name', type: 'text', label: 'Name' },
|
|
||||||
{ name: 'image', type: 'file', label: 'Image', required: false },
|
|
||||||
{ name: 'rating', type: 'number', label: 'Rating', required: false },
|
|
||||||
{ name: 'review', type: 'textarea', label: 'Review', required: false },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
return html(View({ title: 'Create item', body }));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function postItem(req: Request, ctx: RequestCtx) {
|
|
||||||
const form = await req.formData();
|
|
||||||
console.debug({ form });
|
|
||||||
|
|
||||||
return new Response('');
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
mod hello;
|
|
||||||
mod home;
|
|
||||||
mod items;
|
|
||||||
mod shelves;
|
|
||||||
|
|
||||||
use atelier::router::methods::*;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
fn make_handler<Fut, F, R>(f: F) -> Handler
|
|
||||||
where
|
|
||||||
Fut: Future<Output = R> + Send + 'static,
|
|
||||||
F: FnMut(Request, RequestCtx) -> Fut + Clone + Send + Sync + 'static,
|
|
||||||
R: IntoResponse,
|
|
||||||
{
|
|
||||||
Box::new(move |req, ctx| {
|
|
||||||
let mut f = f.clone();
|
|
||||||
Box::pin(async move { f(req, ctx).await.into_response() })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn route(dbs: DbS, req: Request) -> Response {
|
|
||||||
let method = Some(req.method().as_str());
|
|
||||||
let uri = format!("{}", req.uri());
|
|
||||||
let path_parts: Vec<_> = uri.split('/').collect();
|
|
||||||
|
|
||||||
match path_parts.as_slice() {
|
|
||||||
&[] => match method.take() {
|
|
||||||
GET => return home::view(),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match method {
|
|
||||||
None => (http::status::StatusCode::from_u16(405), "Method not allowed").into_response(),
|
|
||||||
Some(_) => (http::status::StatusCode::from_u16(404), "Not found").into_response(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn make_router() -> Router {
|
|
||||||
let mut router = atelier::router::Router::new();
|
|
||||||
|
|
||||||
macro_rules! r {
|
|
||||||
($m:expr, $p:expr, $h:expr) => {
|
|
||||||
router.register($m, $p, make_handler($h))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
r!(GET, "/", home::view);
|
|
||||||
r!(GET, "/shelves", shelves::view);
|
|
||||||
r!(GET, "/shelves/:shelf_xid", shelves::view_one);
|
|
||||||
r!(GET, "/items", items::view);
|
|
||||||
r!(GET, "/items/create", items::view_create);
|
|
||||||
r!(GET, "/items/:item_xid", items::view_one);
|
|
||||||
r!(POST, "/items/create", items::post_create);
|
|
||||||
r!(GET, "/hello/:name", hello::view);
|
|
||||||
|
|
||||||
r!(GET, "/static/*", serve_static);
|
|
||||||
|
|
||||||
router
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn serve_static(req: Request, ctx: RequestCtx) -> Response {
|
|
||||||
let url = req.uri().path();
|
|
||||||
|
|
||||||
"".into_response()
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub async fn view(req: Request, ctx: RequestCtx) -> HandlerResult<String> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn view_one(req: Request, ctx: RequestCtx) -> HandlerResult<String> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn view_create(
|
|
||||||
req: Request,
|
|
||||||
ctx: RequestCtx,
|
|
||||||
) -> HandlerResult<String> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import { RequestCtx } from '@/backend/routes.ts';
|
|
||||||
|
|
||||||
export function viewShelves(req: Request, ctx: RequestCtx) {
|
|
||||||
return new Response('');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function viewShelf(req: Request, ctx: RequestCtx) {
|
|
||||||
return new Response('');
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
export function View(props: { title: string; body: string }) {
|
|
||||||
return `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>${props.title} - Shelves</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
${props.body}
|
|
||||||
<script src="/static/js/bundle.js" type="text/javascript" module></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FormFieldType = 'text' | 'textarea' | 'file' | 'number';
|
|
||||||
export type FormFieldProps = {
|
|
||||||
name: string;
|
|
||||||
type: FormFieldType;
|
|
||||||
label: string;
|
|
||||||
error?: string;
|
|
||||||
required?: boolean;
|
|
||||||
};
|
|
||||||
export function FormField(props: FormFieldProps) {
|
|
||||||
const input = () => {
|
|
||||||
const attrs = `name='${props.name}' id="${props.name}" ${
|
|
||||||
(props.required ?? true) && "required aria-required='true'"
|
|
||||||
}`;
|
|
||||||
if (props.type === 'textarea') {
|
|
||||||
return `<textarea ${attrs}></textarea>`;
|
|
||||||
} else {
|
|
||||||
return `<input type="${props.type}" ${attrs}>`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="form-input">
|
|
||||||
<label for="${props.name}">${props.label}</label>
|
|
||||||
${input()}
|
|
||||||
<div class="error" id="${props.name}-error" aria-live="polite">${
|
|
||||||
props.error ?? ''
|
|
||||||
}</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
export function Form(
|
|
||||||
props: { action: string; fields: FormFieldProps[]; hasFileData?: boolean },
|
|
||||||
) {
|
|
||||||
return `
|
|
||||||
<form action="${props.action}" method="POST" ${
|
|
||||||
props.hasFileData ? "enctype='multipart/form-data'" : ''
|
|
||||||
}>
|
|
||||||
${props.fields.map(FormField).join('')}
|
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub trait Template {
|
|
||||||
fn render(&self, fmt: &mut Formatter) -> FmtResult;
|
|
||||||
|
|
||||||
fn display(self) -> impl Display
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
struct D<T>(T);
|
|
||||||
impl<T: Template> Display for D<T> {
|
|
||||||
fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
|
|
||||||
self.0.render(fmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
D(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TemplateFn<F>(F);
|
|
||||||
pub fn template_fn<F>(f: F) -> TemplateFn<F>
|
|
||||||
where
|
|
||||||
F: Fn(&mut Formatter) -> FmtResult,
|
|
||||||
{
|
|
||||||
TemplateFn(f)
|
|
||||||
}
|
|
||||||
impl<F> Template for TemplateFn<F>
|
|
||||||
where
|
|
||||||
F: Fn(&mut Formatter) -> FmtResult,
|
|
||||||
{
|
|
||||||
fn render(&self, fmt: &mut Formatter) -> FmtResult {
|
|
||||||
self.0(fmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"checkJs": true,
|
|
||||||
"lib": [
|
|
||||||
"esnext",
|
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"deno.window",
|
|
||||||
"deno.unstable"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"tasks": {
|
|
||||||
"run": "deno run -A backend/main.ts",
|
|
||||||
"check": "deno check backend/main.ts",
|
|
||||||
"watch": "deno run --watch -A backend/main.ts",
|
|
||||||
"check-watch": "deno run --check --watch -A backend/main.ts"
|
|
||||||
},
|
|
||||||
"imports": {
|
|
||||||
"@atelier/": "../ts/",
|
|
||||||
"@luca/esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.11.1",
|
|
||||||
"@std/http": "jsr:@std/http",
|
|
||||||
"@db/sqlite": "jsr:@db/sqlite",
|
|
||||||
"@/": "./",
|
|
||||||
"esbuild": "npm:esbuild@^0.24.2",
|
|
||||||
"@htmx": "https://unpkg.com/htmx.org@2.0.4/dist/htmx.js"
|
|
||||||
},
|
|
||||||
"fmt": {
|
|
||||||
"semiColons": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"useTabs": true,
|
|
||||||
"lineWidth": 90
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,289 +0,0 @@
|
||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"specifiers": {
|
|
||||||
"jsr:@db/sqlite@*": "0.12.0",
|
|
||||||
"jsr:@deno/cache-dir@0.13.2": "0.13.2",
|
|
||||||
"jsr:@deno/emit@*": "0.46.0",
|
|
||||||
"jsr:@deno/graph@~0.73.1": "0.73.1",
|
|
||||||
"jsr:@denosaurs/plug@1": "1.0.6",
|
|
||||||
"jsr:@luca/esbuild-deno-loader@*": "0.11.1",
|
|
||||||
"jsr:@luca/esbuild-deno-loader@~0.11.1": "0.11.1",
|
|
||||||
"jsr:@std/assert@0.217": "0.217.0",
|
|
||||||
"jsr:@std/assert@0.221": "0.221.0",
|
|
||||||
"jsr:@std/assert@0.223": "0.223.0",
|
|
||||||
"jsr:@std/bytes@0.223": "0.223.0",
|
|
||||||
"jsr:@std/bytes@^1.0.2": "1.0.4",
|
|
||||||
"jsr:@std/cli@^1.0.6": "1.0.6",
|
|
||||||
"jsr:@std/encoding@0.221": "0.221.0",
|
|
||||||
"jsr:@std/encoding@^1.0.5": "1.0.5",
|
|
||||||
"jsr:@std/fmt@0.221": "0.221.0",
|
|
||||||
"jsr:@std/fmt@0.223": "0.223.0",
|
|
||||||
"jsr:@std/fmt@^1.0.2": "1.0.2",
|
|
||||||
"jsr:@std/fs@0.221": "0.221.0",
|
|
||||||
"jsr:@std/fs@0.223": "0.223.0",
|
|
||||||
"jsr:@std/http@*": "1.0.7",
|
|
||||||
"jsr:@std/io@0.223": "0.223.0",
|
|
||||||
"jsr:@std/media-types@^1.0.3": "1.0.3",
|
|
||||||
"jsr:@std/net@^1.0.4": "1.0.4",
|
|
||||||
"jsr:@std/path@0.217": "0.217.0",
|
|
||||||
"jsr:@std/path@0.221": "0.221.0",
|
|
||||||
"jsr:@std/path@0.223": "0.223.0",
|
|
||||||
"jsr:@std/path@^1.0.6": "1.0.6",
|
|
||||||
"jsr:@std/streams@^1.0.6": "1.0.6",
|
|
||||||
"npm:esbuild@*": "0.24.2",
|
|
||||||
"npm:esbuild@~0.24.2": "0.24.2"
|
|
||||||
},
|
|
||||||
"jsr": {
|
|
||||||
"@db/sqlite@0.12.0": {
|
|
||||||
"integrity": "dd1ef7f621ad50fc1e073a1c3609c4470bd51edc0994139c5bf9851de7a6d85f",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@denosaurs/plug",
|
|
||||||
"jsr:@std/path@0.217"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@deno/cache-dir@0.13.2": {
|
|
||||||
"integrity": "c22419dfe27ab85f345bee487aaaadba498b005cce3644e9d2528db035c5454d",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@deno/graph",
|
|
||||||
"jsr:@std/fmt@0.223",
|
|
||||||
"jsr:@std/fs@0.223",
|
|
||||||
"jsr:@std/io",
|
|
||||||
"jsr:@std/path@0.223"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@deno/emit@0.46.0": {
|
|
||||||
"integrity": "e276be2c77bac1b93caf775762e2a49a54cb00da2d48ca2b01ed8d7cba9d082c",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@deno/cache-dir",
|
|
||||||
"jsr:@std/path@0.223"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@deno/graph@0.73.1": {
|
|
||||||
"integrity": "cd69639d2709d479037d5ce191a422eabe8d71bb68b0098344f6b07411c84d41"
|
|
||||||
},
|
|
||||||
"@denosaurs/plug@1.0.6": {
|
|
||||||
"integrity": "6cf5b9daba7799837b9ffbe89f3450510f588fafef8115ddab1ff0be9cb7c1a7",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/encoding@0.221",
|
|
||||||
"jsr:@std/fmt@0.221",
|
|
||||||
"jsr:@std/fs@0.221",
|
|
||||||
"jsr:@std/path@0.221"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@luca/esbuild-deno-loader@0.11.1": {
|
|
||||||
"integrity": "dc020d16d75b591f679f6b9288b10f38bdb4f24345edb2f5732affa1d9885267",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/bytes@^1.0.2",
|
|
||||||
"jsr:@std/encoding@^1.0.5",
|
|
||||||
"jsr:@std/path@^1.0.6"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/assert@0.217.0": {
|
|
||||||
"integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642"
|
|
||||||
},
|
|
||||||
"@std/assert@0.221.0": {
|
|
||||||
"integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a"
|
|
||||||
},
|
|
||||||
"@std/assert@0.223.0": {
|
|
||||||
"integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24"
|
|
||||||
},
|
|
||||||
"@std/bytes@0.223.0": {
|
|
||||||
"integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8"
|
|
||||||
},
|
|
||||||
"@std/bytes@1.0.4": {
|
|
||||||
"integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc"
|
|
||||||
},
|
|
||||||
"@std/cli@1.0.6": {
|
|
||||||
"integrity": "d22d8b38c66c666d7ad1f2a66c5b122da1704f985d3c47f01129f05abb6c5d3d"
|
|
||||||
},
|
|
||||||
"@std/encoding@0.221.0": {
|
|
||||||
"integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45"
|
|
||||||
},
|
|
||||||
"@std/encoding@1.0.5": {
|
|
||||||
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
|
|
||||||
},
|
|
||||||
"@std/fmt@0.221.0": {
|
|
||||||
"integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a"
|
|
||||||
},
|
|
||||||
"@std/fmt@0.223.0": {
|
|
||||||
"integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208"
|
|
||||||
},
|
|
||||||
"@std/fmt@1.0.2": {
|
|
||||||
"integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7"
|
|
||||||
},
|
|
||||||
"@std/fs@0.221.0": {
|
|
||||||
"integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@0.221",
|
|
||||||
"jsr:@std/path@0.221"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/fs@0.223.0": {
|
|
||||||
"integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c"
|
|
||||||
},
|
|
||||||
"@std/http@1.0.7": {
|
|
||||||
"integrity": "9b904fc256678a5c9759f1a53a24a3fdcc59d83dc62099bb472683b6f819194c",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/cli",
|
|
||||||
"jsr:@std/encoding@^1.0.5",
|
|
||||||
"jsr:@std/fmt@^1.0.2",
|
|
||||||
"jsr:@std/media-types",
|
|
||||||
"jsr:@std/net",
|
|
||||||
"jsr:@std/path@^1.0.6",
|
|
||||||
"jsr:@std/streams"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/io@0.223.0": {
|
|
||||||
"integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@0.223",
|
|
||||||
"jsr:@std/bytes@0.223"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/media-types@1.0.3": {
|
|
||||||
"integrity": "b12d30a7852f7578f4d210622df713bbfd1cbdd9b4ec2eaf5c1845ab70bab159"
|
|
||||||
},
|
|
||||||
"@std/net@1.0.4": {
|
|
||||||
"integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852"
|
|
||||||
},
|
|
||||||
"@std/path@0.217.0": {
|
|
||||||
"integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@0.217"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/path@0.221.0": {
|
|
||||||
"integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@0.221"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/path@0.223.0": {
|
|
||||||
"integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@0.223"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/path@1.0.6": {
|
|
||||||
"integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed"
|
|
||||||
},
|
|
||||||
"@std/streams@1.0.6": {
|
|
||||||
"integrity": "022ed94e380d06b4d91c49eb70241b7289ab78b8c2b4c4bbb7eb265e4997c25c"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm": {
|
|
||||||
"@esbuild/aix-ppc64@0.24.2": {
|
|
||||||
"integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="
|
|
||||||
},
|
|
||||||
"@esbuild/android-arm64@0.24.2": {
|
|
||||||
"integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="
|
|
||||||
},
|
|
||||||
"@esbuild/android-arm@0.24.2": {
|
|
||||||
"integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="
|
|
||||||
},
|
|
||||||
"@esbuild/android-x64@0.24.2": {
|
|
||||||
"integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="
|
|
||||||
},
|
|
||||||
"@esbuild/darwin-arm64@0.24.2": {
|
|
||||||
"integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="
|
|
||||||
},
|
|
||||||
"@esbuild/darwin-x64@0.24.2": {
|
|
||||||
"integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="
|
|
||||||
},
|
|
||||||
"@esbuild/freebsd-arm64@0.24.2": {
|
|
||||||
"integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="
|
|
||||||
},
|
|
||||||
"@esbuild/freebsd-x64@0.24.2": {
|
|
||||||
"integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="
|
|
||||||
},
|
|
||||||
"@esbuild/linux-arm64@0.24.2": {
|
|
||||||
"integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="
|
|
||||||
},
|
|
||||||
"@esbuild/linux-arm@0.24.2": {
|
|
||||||
"integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="
|
|
||||||
},
|
|
||||||
"@esbuild/linux-ia32@0.24.2": {
|
|
||||||
"integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="
|
|
||||||
},
|
|
||||||
"@esbuild/linux-loong64@0.24.2": {
|
|
||||||
"integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="
|
|
||||||
},
|
|
||||||
"@esbuild/linux-mips64el@0.24.2": {
|
|
||||||
"integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="
|
|
||||||
},
|
|
||||||
"@esbuild/linux-ppc64@0.24.2": {
|
|
||||||
"integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="
|
|
||||||
},
|
|
||||||
"@esbuild/linux-riscv64@0.24.2": {
|
|
||||||
"integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="
|
|
||||||
},
|
|
||||||
"@esbuild/linux-s390x@0.24.2": {
|
|
||||||
"integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="
|
|
||||||
},
|
|
||||||
"@esbuild/linux-x64@0.24.2": {
|
|
||||||
"integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="
|
|
||||||
},
|
|
||||||
"@esbuild/netbsd-arm64@0.24.2": {
|
|
||||||
"integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="
|
|
||||||
},
|
|
||||||
"@esbuild/netbsd-x64@0.24.2": {
|
|
||||||
"integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="
|
|
||||||
},
|
|
||||||
"@esbuild/openbsd-arm64@0.24.2": {
|
|
||||||
"integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="
|
|
||||||
},
|
|
||||||
"@esbuild/openbsd-x64@0.24.2": {
|
|
||||||
"integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="
|
|
||||||
},
|
|
||||||
"@esbuild/sunos-x64@0.24.2": {
|
|
||||||
"integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="
|
|
||||||
},
|
|
||||||
"@esbuild/win32-arm64@0.24.2": {
|
|
||||||
"integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="
|
|
||||||
},
|
|
||||||
"@esbuild/win32-ia32@0.24.2": {
|
|
||||||
"integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="
|
|
||||||
},
|
|
||||||
"@esbuild/win32-x64@0.24.2": {
|
|
||||||
"integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="
|
|
||||||
},
|
|
||||||
"esbuild@0.24.2": {
|
|
||||||
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
|
|
||||||
"dependencies": [
|
|
||||||
"@esbuild/aix-ppc64",
|
|
||||||
"@esbuild/android-arm",
|
|
||||||
"@esbuild/android-arm64",
|
|
||||||
"@esbuild/android-x64",
|
|
||||||
"@esbuild/darwin-arm64",
|
|
||||||
"@esbuild/darwin-x64",
|
|
||||||
"@esbuild/freebsd-arm64",
|
|
||||||
"@esbuild/freebsd-x64",
|
|
||||||
"@esbuild/linux-arm",
|
|
||||||
"@esbuild/linux-arm64",
|
|
||||||
"@esbuild/linux-ia32",
|
|
||||||
"@esbuild/linux-loong64",
|
|
||||||
"@esbuild/linux-mips64el",
|
|
||||||
"@esbuild/linux-ppc64",
|
|
||||||
"@esbuild/linux-riscv64",
|
|
||||||
"@esbuild/linux-s390x",
|
|
||||||
"@esbuild/linux-x64",
|
|
||||||
"@esbuild/netbsd-arm64",
|
|
||||||
"@esbuild/netbsd-x64",
|
|
||||||
"@esbuild/openbsd-arm64",
|
|
||||||
"@esbuild/openbsd-x64",
|
|
||||||
"@esbuild/sunos-x64",
|
|
||||||
"@esbuild/win32-arm64",
|
|
||||||
"@esbuild/win32-ia32",
|
|
||||||
"@esbuild/win32-x64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"workspace": {
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@db/sqlite@*",
|
|
||||||
"jsr:@luca/esbuild-deno-loader@~0.11.1",
|
|
||||||
"jsr:@std/http@*",
|
|
||||||
"npm:esbuild@~0.24.2"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import { skibidi } from '@htmx';
|
|
||||||
|
|
||||||
console.debug({ skibidi });
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export function arrayIsEmpty(a: any[]): boolean {
|
|
||||||
return a.length === 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export function sleep(timeoutMs: number): Promise<void> {
|
|
||||||
return new Promise((res) => setTimeout(res, timeoutMs));
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
export type Body = ConstructorParameters<typeof Response>[0];
|
|
||||||
export type ResponseOptions = ConstructorParameters<typeof Response>[1];
|
|
||||||
|
|
||||||
export function badRequest(body?: Body, options?: ResponseOptions): Response {
|
|
||||||
return new Response(body, { status: 400, ...options });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function html(body: Body, options?: ResponseOptions): Response {
|
|
||||||
return new Response(body, {
|
|
||||||
...options,
|
|
||||||
headers: { 'Content-Type': 'text/html', ...options?.headers },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
77
ts/router.ts
77
ts/router.ts
|
|
@ -1,77 +0,0 @@
|
||||||
import { STATUS_CODE } from '@std/http';
|
|
||||||
|
|
||||||
import { OrPromise } from './util.ts';
|
|
||||||
|
|
||||||
export type Method = string;
|
|
||||||
|
|
||||||
export const Method = {
|
|
||||||
GET: 'GET',
|
|
||||||
POST: 'POST',
|
|
||||||
PUT: 'PUT',
|
|
||||||
PATCH: 'PATCH',
|
|
||||||
Any: '___INTERNAL_ANY',
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Handler = (
|
|
||||||
request: Request,
|
|
||||||
pathParams: Record<string, string>,
|
|
||||||
) => OrPromise<Response>;
|
|
||||||
|
|
||||||
export type Match = string | URLPattern;
|
|
||||||
export class Router {
|
|
||||||
#pathnameCache: Map<string, URLPattern>;
|
|
||||||
#routes: Map<URLPattern, Map<Method, Handler>>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.#routes = new Map();
|
|
||||||
this.#pathnameCache = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
register(method: Method, match: Match, handler: Handler) {
|
|
||||||
const urlPattern = (match instanceof URLPattern) ? match : this.#cachePathname(match);
|
|
||||||
if (!this.#routes.has(urlPattern)) {
|
|
||||||
this.#routes.set(urlPattern, new Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
const methodMap = this.#routes.get(urlPattern)!;
|
|
||||||
method = method.toUpperCase();
|
|
||||||
methodMap.set(method, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
#cachePathname(pathname: string) {
|
|
||||||
const cached = this.#pathnameCache.get(pathname);
|
|
||||||
if (cached) {
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pattern = new URLPattern({ pathname });
|
|
||||||
this.#pathnameCache.set(pathname, pattern);
|
|
||||||
|
|
||||||
return pattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
async handle(req: Request): Promise<Response> {
|
|
||||||
const method = req.method.toUpperCase();
|
|
||||||
|
|
||||||
for (const [pattern, methodMap] of this.#routes.entries()) {
|
|
||||||
const match = pattern.exec(req.url);
|
|
||||||
if (!match) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let handler = methodMap.get(method);
|
|
||||||
if (!handler) {
|
|
||||||
handler = methodMap.get(Method.Any);
|
|
||||||
}
|
|
||||||
if (!handler) {
|
|
||||||
return new Response('Method not allowed', {
|
|
||||||
status: STATUS_CODE.MethodNotAllowed,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return handler(req, match.pathname.groups as Record<string, string>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response('Not found', { status: STATUS_CODE.NotFound });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export type OrPromise<T> = T | Promise<T>;
|
|
||||||
Loading…
Reference in a new issue