Create build system (#1)
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
target/
|
||||
dst/
|
848
Cargo.lock
generated
Normal file
848
Cargo.lock
generated
Normal file
@@ -0,0 +1,848 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
|
||||
dependencies = [
|
||||
"askama_derive",
|
||||
"askama_escape",
|
||||
"humansize",
|
||||
"num-traits",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_derive"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
|
||||
dependencies = [
|
||||
"askama_parser",
|
||||
"basic-toml",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_escape"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
|
||||
|
||||
[[package]]
|
||||
name = "askama_parser"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[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 = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inferium"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.170"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core 0.9.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.2.16",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.5.10",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "proc"
|
||||
version = "0.1.0"
|
||||
|
||||
[[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.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sled"
|
||||
version = "0.34.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"fs2",
|
||||
"fxhash",
|
||||
"libc",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.43.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot 0.12.3",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||
|
||||
[[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.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
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"
|
||||
|
||||
[[package]]
|
||||
name = "zmp24"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"inferium",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sled",
|
||||
"tokio",
|
||||
]
|
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "zmp24"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
askama = "0.12.1"
|
||||
bincode = "1.3.3"
|
||||
chrono = "0.4.40"
|
||||
inferium = { path = "./lib/inferium", features = ["async", "tokio-net", "tokio-unixsocks"] }
|
||||
serde = { version = "1.0.218", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
sled = "0.34.7"
|
||||
tokio = { version = "1.43.0", features = ["full"] }
|
||||
|
||||
[features]
|
||||
dev = []
|
42
Makefile
Normal file
42
Makefile
Normal file
@@ -0,0 +1,42 @@
|
||||
.PHONY: build
|
||||
build: \
|
||||
target/release/zmp24
|
||||
|
||||
ARCH := $(shell uname -m)
|
||||
|
||||
MAIN_RS_SRCS := $(shell find src -type f -regex '^.*\.rs$$') Cargo.toml \
|
||||
client/dst/index.html client/dst/script.js client/dst/style.css \
|
||||
config.json
|
||||
|
||||
SEARCH_REPLACE := lib/search_and_replace/target/release/search_and_replace
|
||||
|
||||
.PHONY: run
|
||||
run: $(MAIN_RS_SRCS)
|
||||
DB_PATH="$$(jq -r .dev.db config.json)" \
|
||||
BIND_TO="$$(jq -r .dev.bind_to config.json)" \
|
||||
cargo run --features dev
|
||||
|
||||
.PHONY: clean
|
||||
clean: client_clean
|
||||
cargo clean
|
||||
cd lib/search_and_replace && cargo clean
|
||||
rm -rf dst
|
||||
|
||||
dst:
|
||||
mkdir dst
|
||||
|
||||
include client/client.mk
|
||||
include image/image.mk
|
||||
|
||||
target/release/zmp24: $(MAIN_RS_SRCS)
|
||||
DB_PATH="$$(jq -r .prod.db config.json)" \
|
||||
BIND_TO="$$(jq -r .prod.sock_path config.json)" \
|
||||
cargo build --release
|
||||
|
||||
target/$(ARCH)-unknown-linux-musl/release/zmp24: $(MAIN_RS_SRCS)
|
||||
DB_PATH="$$(jq -r .prod.db config.json)" \
|
||||
BIND_TO="$$(jq -r .prod.sock_path config.json)" \
|
||||
cargo build --target $(ARCH)-unknown-linux-musl --release
|
||||
|
||||
$(SEARCH_REPLACE): lib/search_and_replace/src/main.rs lib/search_and_replace/Cargo.toml
|
||||
cd lib/search_and_replace && cargo build --release
|
2
askama.toml
Normal file
2
askama.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[general]
|
||||
dirs = ["client/dst/"]
|
2
client/.gitignore
vendored
Normal file
2
client/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
dst/
|
BIN
client/bun.lockb
Executable file
BIN
client/bun.lockb
Executable file
Binary file not shown.
33
client/client.mk
Normal file
33
client/client.mk
Normal file
@@ -0,0 +1,33 @@
|
||||
client/node_modules:
|
||||
bun install --cwd client
|
||||
|
||||
client/dst:
|
||||
mkdir client/dst
|
||||
|
||||
client/dst/%.html: \
|
||||
client/src/%.html \
|
||||
client/node_modules \
|
||||
client/dst \
|
||||
$(SEARCH_REPLACE)
|
||||
cat $< | bun run --cwd client html-minifier \
|
||||
--collapse-inline-tag-whitespace \
|
||||
--collapse-boolean-attributes \
|
||||
--collapse-whitespace \
|
||||
--remove-attribute-quotes \
|
||||
--remove-comments \
|
||||
--remove-redundant-attributes | \
|
||||
$(SEARCH_REPLACE) \
|
||||
'##LT##' '<' \
|
||||
'##GT##' '>' \
|
||||
> $@
|
||||
|
||||
client/dst/%.js: client/src/%.ts client/node_modules client/dst
|
||||
bun build $< --minify --outfile $@
|
||||
|
||||
client/dst/%.css: client/src/%.scss client/node_modules client/dst
|
||||
cat $< | bun run --cwd client sass --stdin --style compressed > $@
|
||||
|
||||
.PHONY: client_clean
|
||||
client_clean:
|
||||
rm -rf client/dst
|
||||
rm -rf client/node_modules
|
13
client/package.json
Normal file
13
client/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"html-minifier": "^4.0.0",
|
||||
"sass": "^1.85.1"
|
||||
}
|
||||
}
|
31
client/src/index.html
Normal file
31
client/src/index.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Test page</title>
|
||||
<link href="style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<h1>This is a test page</h1>
|
||||
<p>zmp24 rizzin 2b tru σ pookie fr gooner skibidi</p>
|
||||
<p>This page was visited {{ visit_count }} times before you showed up.</p>
|
||||
<p>
|
||||
Here is {{ visit_count }} dot{% if visit_count != 1 %}s{% endif %}:
|
||||
|
||||
<!--
|
||||
Tohle je skibidi sigma můj úžasný build systém.
|
||||
Ať neděláme gulášek v syntax highlightingu.
|
||||
Ty replacementy jsou v `client/client.mk`.
|
||||
-->
|
||||
{% if visit_count ##LT## 100 %}
|
||||
{% for _ in 0..visit_count %}
|
||||
.
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
That's too much dots to display. So f u.
|
||||
{% endif %}
|
||||
</p>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
16
client/src/script.ts
Normal file
16
client/src/script.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
type Sigma = string;
|
||||
type ToMew = "Σ" | Sigma;
|
||||
|
||||
async function skibidi(): Promise<ToMew> {
|
||||
return "fr fr";
|
||||
}
|
||||
|
||||
async function boomer(): Promise<never> {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
|
||||
async function alpha(): Promise<void> {
|
||||
console.log(await Promise.any([skibidi(), boomer()]) + " nocap");
|
||||
}
|
||||
|
||||
alpha();
|
10
client/src/style.scss
Normal file
10
client/src/style.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
$color-me-daddy: light-dark(black, white);
|
||||
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color: $color-me-daddy;
|
||||
}
|
23
client/tsconfig.json
Normal file
23
client/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": false,
|
||||
"noEmit": true,
|
||||
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
16
config.json
Normal file
16
config.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"prod": {
|
||||
"sock_path": "/run/zmp24.sock",
|
||||
"db": "/data/db"
|
||||
},
|
||||
|
||||
"dev": {
|
||||
"bind_to": "[::1]:8080",
|
||||
"db": "/tmp/zmp24db"
|
||||
},
|
||||
|
||||
"image": {
|
||||
"name": "zmp24",
|
||||
"version": "0.1"
|
||||
}
|
||||
}
|
7
image/image.dockerfile
Normal file
7
image/image.dockerfile
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM docker.io/alpine:latest
|
||||
|
||||
COPY ./zmp24 /usr/bin/zmp24
|
||||
COPY ./run.sh /run.sh
|
||||
RUN apk update && apk add gcompat && mkdir /data
|
||||
|
||||
CMD [ "/run.sh" ]
|
17
image/image.mk
Normal file
17
image/image.mk
Normal file
@@ -0,0 +1,17 @@
|
||||
.PHONY: image
|
||||
image: dst/image/zmp24 dst/image/Containerfile dst/image/run.sh config.json
|
||||
cd dst/image && podman build . -t \
|
||||
"$$(jq -r .image.name ../../config.json):$$(jq -r .image.version ../../config.json)"
|
||||
|
||||
dst/image: dst
|
||||
mkdir dst/image
|
||||
|
||||
dst/image/Containerfile: dst/image image/image.dockerfile
|
||||
ln -sf $$(pwd)/image/image.dockerfile dst/image/Containerfile
|
||||
|
||||
dst/image/run.sh: dst/image image/run.sh
|
||||
ln -f image/run.sh dst/image/run.sh
|
||||
chmod +x dst/image/run.sh
|
||||
|
||||
dst/image/zmp24: dst/image target/$(ARCH)-unknown-linux-musl/release/zmp24
|
||||
ln -f target/$(ARCH)-unknown-linux-musl/release/zmp24 dst/image/zmp24
|
10
image/run.sh
Executable file
10
image/run.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
_term() {
|
||||
kill -TERM "$server_pid" 2>/dev/null
|
||||
}
|
||||
|
||||
trap _term SIGTERM
|
||||
/usr/bin/zmp24 &
|
||||
server_pid=$!
|
||||
wait "$server_pid"
|
8
lib/inferium/.gitignore
vendored
8
lib/inferium/.gitignore
vendored
@@ -1,8 +0,0 @@
|
||||
/target
|
||||
|
||||
|
||||
# Added by cargo
|
||||
#
|
||||
# already existing elements were commented out
|
||||
|
||||
#/target
|
@@ -1,60 +0,0 @@
|
||||
#![feature(test)]
|
||||
extern crate test;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use test::Bencher;
|
||||
|
||||
extern crate inferium;
|
||||
use inferium::h1::{SyncClient, Response, ResponseHead, ProtocolVariant};
|
||||
use inferium::{Status, HeaderValue};
|
||||
use inferium::TestSyncStream;
|
||||
|
||||
fn parse_response_sync_inner() {
|
||||
let src = "HTTP/1.1 200 OK\r\nserver: inferium\r\n\r\n".as_bytes().to_vec();
|
||||
let stream = TestSyncStream::<4>::new(&src);
|
||||
let mut client = SyncClient::<TestSyncStream<4>>::new(stream);
|
||||
let target = Response::HeadersOnly(ResponseHead::new(
|
||||
Status::Ok,
|
||||
ProtocolVariant::HTTP1_1,
|
||||
HashMap::from([
|
||||
("server".into(), HeaderValue::new(vec!["inferium".to_string()]))
|
||||
])
|
||||
));
|
||||
assert_eq!(client.receive_response().unwrap(), target);
|
||||
}
|
||||
|
||||
fn parse_response_sync_inner_body() {
|
||||
let mut src = "HTTP/1.1 200 OK\r\ncontent-length: 50\r\n\r\n".as_bytes().to_vec();
|
||||
src.extend_from_slice(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
let stream = TestSyncStream::<4>::new(&src);
|
||||
let mut client = SyncClient::<TestSyncStream<4>>::new(stream);
|
||||
let target_head = ResponseHead::new(
|
||||
Status::Ok,
|
||||
ProtocolVariant::HTTP1_1,
|
||||
HashMap::from([
|
||||
("content-length".into(), HeaderValue::new(vec!["50".to_string()]))
|
||||
])
|
||||
);
|
||||
let Response::WithSizedBody((h, mut b)) = client.receive_response().unwrap() else {
|
||||
panic!();
|
||||
};
|
||||
let b = b.recv_all().unwrap();
|
||||
assert_eq!(h, target_head);
|
||||
assert_eq!(b, b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn parse_response_sync(b: &mut Bencher) {
|
||||
b.bytes = 37;
|
||||
b.iter(|| {
|
||||
parse_response_sync_inner();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn parse_response_sync_with_body(b: &mut Bencher) {
|
||||
b.bytes = 39 + 13;
|
||||
b.iter(|| {
|
||||
parse_response_sync_inner_body();
|
||||
});
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
#![feature(test)]
|
||||
extern crate test;
|
||||
use test::Bencher;
|
||||
|
||||
extern crate inferium;
|
||||
use inferium::Status;
|
||||
|
||||
#[bench]
|
||||
fn baseline(b: &mut Bencher) {
|
||||
fn status_test_from_slice(raw: &[u8]) -> Result<Status, ()> {
|
||||
match raw {
|
||||
b"200" => Ok(Status::Ok),
|
||||
b"404" => Ok(Status::NotFound),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
assert_eq!(status_test_from_slice(b"200".as_slice()), Ok(Status::Ok));
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn valid_ok(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
assert_eq!(Status::try_from(b"200".as_slice()), Ok(Status::Ok));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn valid_internal_server_error(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
assert_eq!(Status::try_from(b"500".as_slice()), Ok(Status::InternalServerError));
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn invalid(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
assert!(Status::try_from(b"690".as_slice()).is_err());
|
||||
})
|
||||
}
|
@@ -1,94 +0,0 @@
|
||||
// This is an async port of `examples/simple_server.rs`. Please see that example first.
|
||||
//
|
||||
// Features `async` and `tokio-net` must be enabled for this example to compile.
|
||||
// It is also possible to enable feature `full` (which will enable all the features).
|
||||
|
||||
use std::{collections::HashMap, net::SocketAddr};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use inferium::{
|
||||
h1::{ProtocolVariant, Request, ResponseHead, ServerSendError, AsyncServer},
|
||||
HeaderKey, Method, Status, TokioInet
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let listener = TcpListener::bind("localhost:8080").await.unwrap();
|
||||
loop {
|
||||
let (conn, addr) = listener.accept().await.unwrap();
|
||||
// Here we are creating a new asynchronous task for every client.
|
||||
// This will fork off in an asynchronous manner and won't block our accept loop.
|
||||
tokio::task::spawn(async move {
|
||||
// We created this new async block, so we need to `.await` on this function to propagate
|
||||
// the future from the function to the top of the spawned task (this async block).
|
||||
handle_client(conn, addr).await;
|
||||
});
|
||||
// You can now handle multiple clients at once... congratulations.
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_client(conn: TcpStream, addr: SocketAddr) {
|
||||
println!("connection from {addr:?}");
|
||||
let mut server_handler = AsyncServer::<TokioInet>::new(TokioInet::new(conn));
|
||||
// When receiving or sending - we call the same functions with `.await` appended (in an async
|
||||
// context). This will automatically poll the returned future from the top of the context.
|
||||
// The polling is handled by tokio here - so we don't need to worry about it.
|
||||
while let Ok(request) = server_handler.receive_request().await {
|
||||
match handle_request(request, addr) {
|
||||
Ok((h, b)) => if let Err(_) = send_response(h, b, &mut server_handler).await { break; },
|
||||
Err(()) => break,
|
||||
}
|
||||
};
|
||||
println!("ended connection for {addr:?}");
|
||||
}
|
||||
|
||||
fn handle_request<T>(
|
||||
req: Request<T>, addr: SocketAddr
|
||||
) -> Result<(ResponseHead, &'static [u8]), ()> {
|
||||
let Request::HeadersOnly(headers) = req else {
|
||||
return Err(());
|
||||
};
|
||||
|
||||
println!("req from {addr:?}: {headers}");
|
||||
|
||||
const OK_RESPONSE: &[u8] = b"<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, world!</h1>
|
||||
<p>Hello from inferium.</p>
|
||||
</body>
|
||||
</html>";
|
||||
const NOT_FOUND_RESPONSE: &[u8] = b"<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Not found</h1>
|
||||
<p>This page was not found</p>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
Ok(match (headers.method(), headers.uri().path()) {
|
||||
(&Method::GET, "/") => (ResponseHead::new(
|
||||
Status::Ok,
|
||||
ProtocolVariant::HTTP1_0,
|
||||
HashMap::from([
|
||||
(HeaderKey::SERVER, "inferium".parse().unwrap()),
|
||||
(HeaderKey::CONTENT_LENGTH, OK_RESPONSE.len().into()),
|
||||
])
|
||||
), OK_RESPONSE),
|
||||
|
||||
_ => (ResponseHead::new(
|
||||
Status::NotFound,
|
||||
ProtocolVariant::HTTP1_0,
|
||||
HashMap::from([
|
||||
(HeaderKey::SERVER, "inferium".parse().unwrap()),
|
||||
(HeaderKey::CONTENT_LENGTH, NOT_FOUND_RESPONSE.len().into()),
|
||||
])
|
||||
), NOT_FOUND_RESPONSE),
|
||||
})
|
||||
}
|
||||
|
||||
async fn send_response(
|
||||
response: ResponseHead, body: &[u8], conn: &mut AsyncServer<TokioInet>
|
||||
)-> Result<(), ServerSendError> {
|
||||
conn.send_response(&response).await?;
|
||||
conn.send_body_bytes(body).await.map_err(|e| e.try_into().unwrap())
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
// This is a port of a client from `examples/start_here.rs`. Please see that example first.
|
||||
// Also... maybe brush up on some async tasks since we are going to need them here (there is an
|
||||
// example on async with inferium in `examples/going_async.rs`).
|
||||
//
|
||||
// Features `async`, `tokio-net` and `webpki-roots` dependency must be enabled for this example to
|
||||
// compile. We recommend enabling the `dev` feature when running this example.
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_rustls::{
|
||||
rustls::{
|
||||
pki_types::ServerName,
|
||||
ClientConfig,
|
||||
RootCertStore
|
||||
},
|
||||
TlsConnector,
|
||||
TlsStream
|
||||
};
|
||||
use inferium::{
|
||||
h1::{
|
||||
ProtocolVariant,
|
||||
RequestHead,
|
||||
Response,
|
||||
AsyncClient
|
||||
},
|
||||
HeaderKey,
|
||||
Method,
|
||||
TokioRustls
|
||||
};
|
||||
|
||||
async fn run_tls_handshake(raw_stream: TcpStream) -> TlsStream<TcpStream> {
|
||||
let mut root_certs = RootCertStore::empty();
|
||||
root_certs.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
let config = ClientConfig::builder()
|
||||
.with_root_certificates(root_certs)
|
||||
.with_no_client_auth();
|
||||
let connector = TlsConnector::from(Arc::new(config));
|
||||
let verify_server_name = ServerName::try_from("zumepro.cz").unwrap();
|
||||
TlsStream::Client(connector.connect(verify_server_name, raw_stream).await.unwrap())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let stream = TcpStream::connect("zumepro.cz:443").await.unwrap();
|
||||
let stream = run_tls_handshake(stream).await;
|
||||
let conn = TokioRustls::new(stream);
|
||||
let mut client = AsyncClient::<TokioRustls>::new(conn);
|
||||
|
||||
let to_send = RequestHead::new(
|
||||
Method::GET, "/".parse().unwrap(), ProtocolVariant::HTTP1_1,
|
||||
HashMap::from([
|
||||
(HeaderKey::USER_AGENT, "Mozilla/5.0 (inferium)".parse().unwrap()),
|
||||
(HeaderKey::HOST, "zumepro.cz".parse().unwrap()),
|
||||
(HeaderKey::CONNECTION, "close".parse().unwrap())
|
||||
])
|
||||
);
|
||||
println!("----------> Sending\n\n{to_send}\n");
|
||||
client.send_request(&to_send).await.unwrap();
|
||||
let response = client.receive_response().await.unwrap();
|
||||
|
||||
let (header, body) = match response {
|
||||
Response::HeadersOnly(h) => (h, None),
|
||||
Response::WithSizedBody((_, _)) => panic!(),
|
||||
Response::WithChunkedBody((h, b)) => (h, Some(b)),
|
||||
};
|
||||
|
||||
println!("----------< Received\n\n{header}\n");
|
||||
|
||||
if let Some(mut body) = body {
|
||||
|
||||
// Since our zumepro server sends bodies chunked - we will need to handle it.
|
||||
// This simple loop just collects all the chunks into the res vector.
|
||||
let mut res = Vec::new();
|
||||
while let Some(mut chunk) = body.get_chunk_async().await.unwrap() {
|
||||
// Now here is a difference between sync and async.
|
||||
//
|
||||
// For easy body manipulation and no redundant trait pollution, a body within an
|
||||
// asynchronous stream can be sent/received using the same methods as a synchronous one,
|
||||
// but with the suffix `_async`.
|
||||
res.append(&mut chunk.recv_all_async().await.unwrap());
|
||||
}
|
||||
|
||||
println!(
|
||||
"----------< Body\n\n{:?}\n",
|
||||
std::str::from_utf8(&res).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,84 +0,0 @@
|
||||
use std::{collections::HashMap, net::{SocketAddr, TcpListener}};
|
||||
use inferium::{
|
||||
h1::{ProtocolVariant, Request, ResponseHead, ServerSendError, SyncServer},
|
||||
HeaderKey, Method, Status, StdInet
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let listener = TcpListener::bind("localhost:8080").unwrap();
|
||||
loop {
|
||||
let (conn, addr) = listener.accept().unwrap();
|
||||
println!("connection from {addr:?}");
|
||||
let mut server_handler = SyncServer::<StdInet>::new(StdInet::new(conn));
|
||||
// We'll serve the client as long as it sends valid requests.
|
||||
// Note that this will effectively block other clients.
|
||||
while let Ok(request) = server_handler.receive_request() {
|
||||
// This matching is here to provide a way of controlling the while loop.
|
||||
match handle_request(request, addr) {
|
||||
Ok((h, b)) => if let Err(_) = send_response(h, b, &mut server_handler) { break; },
|
||||
Err(()) => break,
|
||||
}
|
||||
};
|
||||
println!("ended connection for {addr:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_request<T>(
|
||||
req: Request<T>, addr: SocketAddr
|
||||
) -> Result<(ResponseHead, &'static [u8]), ()> {
|
||||
let Request::HeadersOnly(headers) = req else {
|
||||
// We will not handle POST requests with bodies - so let's tell the client to f*ck off.
|
||||
return Err(());
|
||||
};
|
||||
|
||||
println!("req from {addr:?}: {headers}");
|
||||
|
||||
const OK_RESPONSE: &[u8] = b"<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, world!</h1>
|
||||
<p>Hello from inferium.</p>
|
||||
</body>
|
||||
</html>";
|
||||
const NOT_FOUND_RESPONSE: &[u8] = b"<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Not found</h1>
|
||||
<p>This page was not found</p>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
// The URI can contain both path and parameters - so we're just getting the path here.
|
||||
Ok(match (headers.method(), headers.uri().path()) {
|
||||
// The ok response with our index page
|
||||
(&Method::GET, "/") => (ResponseHead::new(
|
||||
Status::Ok,
|
||||
ProtocolVariant::HTTP1_0,
|
||||
HashMap::from([
|
||||
(HeaderKey::SERVER, "inferium".parse().unwrap()),
|
||||
(HeaderKey::CONTENT_LENGTH, OK_RESPONSE.len().into()),
|
||||
])
|
||||
), OK_RESPONSE),
|
||||
|
||||
// The not found response with an example not found page
|
||||
_ => (ResponseHead::new(
|
||||
Status::NotFound,
|
||||
ProtocolVariant::HTTP1_0,
|
||||
HashMap::from([
|
||||
(HeaderKey::SERVER, "inferium".parse().unwrap()),
|
||||
(HeaderKey::CONTENT_LENGTH, NOT_FOUND_RESPONSE.len().into()),
|
||||
])
|
||||
), NOT_FOUND_RESPONSE),
|
||||
})
|
||||
}
|
||||
|
||||
fn send_response(
|
||||
response: ResponseHead, body: &[u8], conn: &mut SyncServer<StdInet>
|
||||
)-> Result<(), ServerSendError> {
|
||||
conn.send_response(&response)?;
|
||||
// The send body can fail on an I/O error or if the content-length header does not match the
|
||||
// actual sent length in this scenario. But we know that we have the correct length so with
|
||||
// `.try_into().unwrap()` we tell inferium to convert the error and panic on (not so much)
|
||||
// possible body length discrepancy.
|
||||
conn.send_body_bytes(body).map_err(|e| e.try_into().unwrap())
|
||||
}
|
@@ -1,101 +0,0 @@
|
||||
// Hello, and welcome to inferium. A performance-oriented small HTTP library written in Rust that
|
||||
// keeps you (the user) in charge.
|
||||
|
||||
// Let's first import some necessary things.
|
||||
|
||||
// In inferium - HashMaps are used to store uri parameters and headers.
|
||||
use std::collections::HashMap;
|
||||
// TcpStream is needed if we want to connect to the internet.
|
||||
use std::net::TcpStream;
|
||||
|
||||
use inferium::{
|
||||
// The h1 module contains all the protocol specific things for HTTP/1.(0/1).
|
||||
h1::{
|
||||
// ProtocolVariant contains variants with the protocol versions supported in this module:
|
||||
// - HTTP/1.1
|
||||
// - HTTP/1.0
|
||||
ProtocolVariant,
|
||||
// RequestHead contains headers and the HTTP request headline (method, path, protocol).
|
||||
RequestHead,
|
||||
// Response here is a wrapper for a response that can have the following:
|
||||
// - Headers only
|
||||
// - Headers and body (with a known length or chunked)
|
||||
//
|
||||
// ! The body in the response is not yet collected. It is up to you if you wish to discard
|
||||
// the connection or receive and collect the response body into some structure.
|
||||
//
|
||||
// The same things here go for the Request object which is nearly the same except that the
|
||||
// headline contains protocol and status instead.
|
||||
Response,
|
||||
// Sync client is a stream wrapper that helps us keep track of the open connection and
|
||||
// perform request/response operations.
|
||||
//
|
||||
// The server equivalent is SyncServer.
|
||||
SyncClient
|
||||
},
|
||||
// Header key contains various known header keys, but can also store arbitrary (unknown) header
|
||||
// key in the OTHER variant.
|
||||
HeaderKey,
|
||||
Method,
|
||||
// StdInet here is a stream wrapper that allows the TcpStream to be used by inferium. There is
|
||||
// also a unix socket equivalent and some asynchronous io wrappers.
|
||||
StdInet
|
||||
};
|
||||
|
||||
fn main() {
|
||||
// Let's first create a connection... nothing weird here.
|
||||
let conn = StdInet::new(TcpStream::connect("zumepro.cz:80").unwrap());
|
||||
// And a client...
|
||||
let mut client = SyncClient::<StdInet>::new(conn);
|
||||
|
||||
// Now let's create a request to send
|
||||
let to_send = RequestHead::new(
|
||||
// The path here is parsed into an HTTP path object (which also supports parameters)
|
||||
// I'm using HTTP/1.0 in this example as HTTP/1.1 automatically infers a compatibility with
|
||||
// chunked encoding (which I'm not even trying to handle here).
|
||||
Method::GET, "/".parse().unwrap(), ProtocolVariant::HTTP1_0,
|
||||
HashMap::from([
|
||||
// All headers are HeaderKey - HeaderValue pairs. We can parse the header value into
|
||||
// the desired object.
|
||||
//
|
||||
// Constructing arbitrary header key is supported using the OTHER variant - however
|
||||
// it's not recommended as a violation of the HTTP protocol can happen.
|
||||
//
|
||||
// If you really want to construct an arbitrary header key - please carefully check
|
||||
// that all of the symbols are valid.
|
||||
(HeaderKey::USER_AGENT, "Mozilla/5.0 (inferium)".parse().unwrap()),
|
||||
(HeaderKey::HOST, "zumepro.cz".parse().unwrap()),
|
||||
])
|
||||
);
|
||||
println!("----------> Sending\n\n{to_send}\n");
|
||||
// Let's send the request - this is pretty straightforward.
|
||||
client.send_request(&to_send).unwrap();
|
||||
// As is receiving a response.
|
||||
let response = client.receive_response().unwrap();
|
||||
|
||||
// Now (as we discussed earlier) - the response can have a body.
|
||||
// In this example we'll try to handle a basic body with a known size.
|
||||
let (header, body) = match response {
|
||||
// Extracting the headers if no body is present.
|
||||
Response::HeadersOnly(h) => (h, None),
|
||||
// Extracting both the headers and the body if body is present.
|
||||
Response::WithSizedBody((h, b)) => (h, Some(b)),
|
||||
// We will not handle chunked responses in this example.
|
||||
Response::WithChunkedBody((_, _)) => panic!(),
|
||||
};
|
||||
|
||||
// inferium kindly provides a simple way to print the head of a request/response.
|
||||
// It will be formatted pretty close to the actual protocol plaintext representation.
|
||||
println!("----------< Received\n\n{header}\n");
|
||||
|
||||
// And finally... if we have a body, we'll print it.
|
||||
if let Some(mut body) = body {
|
||||
println!(
|
||||
"----------< Body\n\n{:?}\n",
|
||||
// A body is always returned in bytes. It's up to you to decode it however you see fit.
|
||||
std::str::from_utf8(&mut body.recv_all().unwrap()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
// And you're done. Come on... try to run it.
|
||||
}
|
@@ -158,6 +158,14 @@ impl HeaderValue {
|
||||
self.inner.push(val);
|
||||
}
|
||||
|
||||
/// Create a new unchecked value.
|
||||
/// This is faster, but can cause protocol violation.
|
||||
/// Please avoid putting unchecked content here.
|
||||
#[inline]
|
||||
pub fn new_unchecked(inner: String) -> Self {
|
||||
Self { inner: vec![inner] }
|
||||
}
|
||||
|
||||
/// Query the first entry for this header key.
|
||||
///
|
||||
/// See the documentation of [`HeaderValue`] for more information.
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod settings;
|
||||
|
||||
mod io;
|
||||
|
@@ -322,6 +322,28 @@ pub enum Request<'a, T> {
|
||||
WithChunkedBody((RequestHead, Incoming<'a, ChunkedIn<'a, PrependableStream<T>>>)),
|
||||
}
|
||||
|
||||
impl<T> Response<'_, T> {
|
||||
#[inline]
|
||||
pub fn head(&self) -> &ResponseHead {
|
||||
match self {
|
||||
Self::HeadersOnly(h) => h,
|
||||
Self::WithSizedBody((h, _)) => h,
|
||||
Self::WithChunkedBody((h, _)) => h,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Request<'_, T> {
|
||||
#[inline]
|
||||
pub fn head(&self) -> &RequestHead {
|
||||
match self {
|
||||
Self::HeadersOnly(h) => h,
|
||||
Self::WithSizedBody((h, _)) => h,
|
||||
Self::WithChunkedBody((h, _)) => h,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_outgoing_req_content_length(head: &RequestHead) -> Result<Option<usize>, ClientSendError> {
|
||||
let Some(l) = head.headers.get(&HeaderKey::CONTENT_LENGTH) else {
|
||||
return Ok(None);
|
||||
|
7
lib/search_and_replace/Cargo.lock
generated
Normal file
7
lib/search_and_replace/Cargo.lock
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "search_and_replace"
|
||||
version = "0.1.0"
|
10
lib/search_and_replace/Cargo.toml
Normal file
10
lib/search_and_replace/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "search_and_replace"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
386
lib/search_and_replace/src/main.rs
Normal file
386
lib/search_and_replace/src/main.rs
Normal file
@@ -0,0 +1,386 @@
|
||||
use std::{env::args, io::Read};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct JumpTable {
|
||||
jumps: Vec<usize>,
|
||||
}
|
||||
|
||||
impl JumpTable {
|
||||
/// Returns the pointer for the next char (if this char is matched).
|
||||
fn resolve_char(
|
||||
pat: &Vec<char>,
|
||||
ptr: &usize,
|
||||
cur_char: &char,
|
||||
) -> usize {
|
||||
if pat.get(*ptr).unwrap() == cur_char {
|
||||
return *ptr + 1;
|
||||
}
|
||||
if pat.get(0).unwrap() == cur_char {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn new(pat: &Vec<char>) -> Self {
|
||||
if pat.len() == 0 {
|
||||
return Self { jumps: vec![] };
|
||||
}
|
||||
let mut ptr = 0_usize;
|
||||
let mut jumps = vec![0];
|
||||
for cur_char in pat.iter().skip(1) {
|
||||
ptr = JumpTable::resolve_char(pat, &ptr, cur_char);
|
||||
jumps.push(ptr);
|
||||
}
|
||||
|
||||
Self { jumps }
|
||||
}
|
||||
|
||||
pub fn search(&self, pat: &Vec<char>, needle: &char, ptr: &usize) -> usize {
|
||||
let mut ptr = *ptr;
|
||||
while ptr != 0 {
|
||||
if pat.get(ptr).unwrap() == needle {
|
||||
break;
|
||||
}
|
||||
ptr = *self.jumps.get(ptr.saturating_sub(1)).unwrap();
|
||||
}
|
||||
ptr
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Pattern {
|
||||
pat: Vec<char>,
|
||||
jt: JumpTable,
|
||||
ptr: usize,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
pub fn new(pat: String) -> Result<Self, ()> {
|
||||
if pat.len() == 0 {
|
||||
return Err(());
|
||||
}
|
||||
let pat = pat.chars().collect::<Vec<_>>();
|
||||
let jt = JumpTable::new(&pat);
|
||||
Ok(Self {
|
||||
pat,
|
||||
jt,
|
||||
ptr: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn search_and_update(&mut self, cur_char: &char) -> bool {
|
||||
if self.pat.get(self.ptr).unwrap() == cur_char {
|
||||
self.ptr += 1;
|
||||
return if self.ptr == self.pat.len() { true } else { false };
|
||||
}
|
||||
let found = self.jt.search(&self.pat, &cur_char, &self.ptr);
|
||||
self.ptr = found;
|
||||
false
|
||||
}
|
||||
|
||||
pub fn reset_ptr(&mut self) {
|
||||
self.ptr = 0;
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.pat.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct Match {
|
||||
pub pat_id: usize,
|
||||
pub start_pos: usize,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
impl Match {
|
||||
pub fn new(pat_id: usize, start_pos: usize, len: usize) -> Self {
|
||||
Self { pat_id, start_pos, len }
|
||||
}
|
||||
}
|
||||
|
||||
/// [`MPSearch`] assumes that each [`Match`] will be replaced later. So this will match the first
|
||||
/// pattern only and reset all the pointers (including the matched one) to zero.
|
||||
#[derive(Debug)]
|
||||
struct MPSearch {
|
||||
pats: Vec<Pattern>,
|
||||
}
|
||||
|
||||
impl MPSearch {
|
||||
/// The [`Pattern`]s will be searched in the order as they are in the [`Vec`].
|
||||
pub fn new(pats: Vec<Pattern>) -> Self {
|
||||
Self { pats }
|
||||
}
|
||||
|
||||
/// Reset pointers of all patterns
|
||||
fn reset_all_ptrs(&mut self) {
|
||||
for pat in self.pats.iter_mut() {
|
||||
pat.reset_ptr();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if ANY pattern ended a match at this pos
|
||||
///
|
||||
/// This will also clear all ptrs on a match
|
||||
fn search_at_pos(&mut self, cur_char: &char, pos: &usize) -> Option<Match> {
|
||||
let mut cur_match = None;
|
||||
for (idx, pat) in self.pats.iter_mut().enumerate() {
|
||||
if pat.search_and_update(cur_char) {
|
||||
cur_match = Some(Match::new(idx, pos.saturating_sub(pat.len() - 1), pat.len()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(_) = cur_match {
|
||||
self.reset_all_ptrs();
|
||||
}
|
||||
cur_match
|
||||
}
|
||||
|
||||
/// Perform a search on the haystack - returning the ordered [`Match`]es.
|
||||
pub fn search(&mut self, haystack: &String) -> Vec<Match> {
|
||||
let mut res = Vec::new();
|
||||
for (cur_idx, cur_char) in haystack.chars().enumerate() {
|
||||
if let Some(cur_match) = self.search_at_pos(&cur_char, &cur_idx) {
|
||||
res.push(cur_match);
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! skip_n {
|
||||
($iter: ident, $n: expr) => {
|
||||
for _ in 0..$n {
|
||||
$iter.next().ok_or(())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! append_n {
|
||||
($iter: ident, $target: ident, $n: expr) => {
|
||||
for _ in 0..$n {
|
||||
$target.push($iter.next().ok_or(())?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Replace the given [`Match`]es with the replacements. The replacements need to be in the order
|
||||
/// of the search patterns as when [`MPSearch`] was constructed.
|
||||
#[derive(Debug)]
|
||||
struct MPReplace {
|
||||
replacements: Vec<String>,
|
||||
matches: Vec<Match>,
|
||||
}
|
||||
|
||||
impl MPReplace {
|
||||
/// The replacement must be given in the same order as the patterns were on the corresponding
|
||||
/// [`MPSearch`] construction.
|
||||
pub fn new(matches: Vec<Match>, replacements: Vec<String>) -> Self {
|
||||
Self { replacements, matches }
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
/// This can fail if the [`Match`]es are invalid relevant to the given target.
|
||||
pub fn replace(&self, target: &String) -> Result<String, ()> {
|
||||
let mut ptr = 0;
|
||||
let mut iter = target.chars();
|
||||
let mut res = String::new();
|
||||
for cur_match in self.matches.iter() {
|
||||
append_n!(iter, res, cur_match.start_pos - ptr);
|
||||
skip_n!(iter, cur_match.len);
|
||||
res.push_str(self.replacements.get(cur_match.pat_id).ok_or(())?);
|
||||
ptr = cur_match.start_pos + cur_match.len;
|
||||
}
|
||||
append_n!(iter, res, target.chars().count() - ptr);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
struct PatMatchArgs<I: Iterator<Item = String>> {
|
||||
inner: I,
|
||||
}
|
||||
|
||||
impl<I: Iterator<Item = String>> PatMatchArgs<I> {
|
||||
pub fn new(inner: I, count: usize) -> Result<Self, ()> {
|
||||
if count % 2 != 0 {
|
||||
return Err(());
|
||||
}
|
||||
Ok(Self { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Iterator<Item = String>> Iterator for PatMatchArgs<I> {
|
||||
type Item = (String, String);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
Some((self.inner.next()?, self.inner.next()?))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut contents = Vec::new();
|
||||
std::io::stdin().lock().read_to_end(&mut contents).unwrap();
|
||||
let contents = std::str::from_utf8(&contents).unwrap().to_string();
|
||||
|
||||
let args = match PatMatchArgs::new(args().skip(1), args().len() - 1) {
|
||||
Ok(val) => val,
|
||||
Err(()) => {
|
||||
eprintln!("the number of arguments after filepath must be divisible by two");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let mut pats = Vec::new();
|
||||
let mut replacements = Vec::new();
|
||||
for (cur_pat, cur_replacement) in args {
|
||||
let Ok(cur_pat) = Pattern::new(cur_pat) else {
|
||||
eprintln!("the patterns can't be empty");
|
||||
std::process::exit(1);
|
||||
};
|
||||
pats.push(cur_pat);
|
||||
replacements.push(cur_replacement);
|
||||
}
|
||||
|
||||
let matches = MPSearch::new(pats).search(&contents);
|
||||
let replaced = MPReplace::new(matches, replacements)
|
||||
.replace(&contents).unwrap();
|
||||
println!("{}", replaced);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{JumpTable, MPReplace, MPSearch, Match, Pattern};
|
||||
|
||||
#[test]
|
||||
fn jumps_01() {
|
||||
let src = String::from("thisthen").chars().collect::<Vec<_>>();
|
||||
let target = vec![0_usize, 0, 0, 0, 1, 2, 0, 0];
|
||||
let jt = JumpTable::new(&src);
|
||||
assert_eq!(jt.jumps, target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jumps_02() {
|
||||
let src = String::from("tthis").chars().collect::<Vec<_>>();
|
||||
let target = vec![0, 1, 0, 0, 0];
|
||||
let jt = JumpTable::new(&src);
|
||||
assert_eq!(jt.jumps, target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jumps_03() {
|
||||
let src = String::from("t").chars().collect::<Vec<_>>();
|
||||
let target = vec![0];
|
||||
let jt = JumpTable::new(&src);
|
||||
assert_eq!(jt.jumps, target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jumps_04() {
|
||||
let src = String::from("tt").chars().collect::<Vec<_>>();
|
||||
let target = vec![0, 1];
|
||||
let jt = JumpTable::new(&src);
|
||||
assert_eq!(jt.jumps, target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jumps_05() {
|
||||
let src = String::from("").chars().collect::<Vec<_>>();
|
||||
let target = vec![];
|
||||
let jt = JumpTable::new(&src);
|
||||
assert_eq!(jt.jumps, target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_01() {
|
||||
let pat = String::from("tthis").chars().collect::<Vec<_>>();
|
||||
let jt = JumpTable::new(&pat);
|
||||
assert_eq!(jt.search(&pat, &'t', &1), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_02() {
|
||||
let pat = String::from("testtesa").chars().collect::<Vec<_>>();
|
||||
let jt = JumpTable::new(&pat);
|
||||
assert_eq!(jt.search(&pat, &'t', &7), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_03() {
|
||||
let pat = String::from("ahojahoj").chars().collect::<Vec<_>>();
|
||||
let jt = JumpTable::new(&pat);
|
||||
assert_eq!(jt.search(&pat, &'j', &7), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_04() {
|
||||
let pat = String::from("ahojahoj").chars().collect::<Vec<_>>();
|
||||
let jt = JumpTable::new(&pat);
|
||||
assert_eq!(jt.search(&pat, &'j', &7), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_05() {
|
||||
let pat = String::from("jojojojojojoja").chars().collect::<Vec<_>>();
|
||||
let jt = JumpTable::new(&pat);
|
||||
assert_eq!(jt.search(&pat, &'o', &13), 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_and_update_01() {
|
||||
let pat = String::from("test");
|
||||
let mut pat = Pattern::new(pat).unwrap();
|
||||
let haystack = String::from("thisisatest");
|
||||
for cur_haystack in haystack.chars().take(haystack.len() - 1) {
|
||||
assert_eq!(pat.search_and_update(&cur_haystack), false);
|
||||
println!("{:?}", pat);
|
||||
}
|
||||
println!("{:?}", pat);
|
||||
assert_eq!(pat.search_and_update(&'t'), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_pattern() {
|
||||
assert_eq!(Pattern::new("".chars().collect()), Err(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpsearch_01() {
|
||||
let mut mpsearch = MPSearch::new(
|
||||
vec!["this", "is", "a", "test"]
|
||||
.iter()
|
||||
.map(|p| Pattern::new(p.to_string()).unwrap())
|
||||
.collect()
|
||||
);
|
||||
println!("{:?}", mpsearch);
|
||||
let haystack = String::from("this is a test");
|
||||
let target = vec![
|
||||
Match::new(0, 0, 4),
|
||||
Match::new(1, 5, 2),
|
||||
Match::new(2, 8, 1),
|
||||
Match::new(3, 10, 4)
|
||||
];
|
||||
assert_eq!(mpsearch.search(&haystack), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpreplace_01() {
|
||||
let mut mpsearch = MPSearch::new(
|
||||
vec!["this", "is", "a", "test"]
|
||||
.iter()
|
||||
.map(|p| Pattern::new(p.to_string()).unwrap())
|
||||
.collect()
|
||||
);
|
||||
let haystack = String::from("this-is.a*test///");
|
||||
let matches = mpsearch.search(&haystack);
|
||||
let mpreplace = MPReplace::new(matches, vec![
|
||||
"that".to_string(),
|
||||
"isn't".to_string(),
|
||||
"the".to_string(),
|
||||
"final".to_string(),
|
||||
]);
|
||||
let replaced = mpreplace.replace(&haystack).unwrap();
|
||||
assert_eq!(replaced, "that-isn't.the*final///");
|
||||
}
|
||||
}
|
25
src/logger.rs
Normal file
25
src/logger.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
#[macro_export]
|
||||
macro_rules! logger {
|
||||
(@inner $fixed: literal, $msg: literal$(, $($arg: expr),*)?) => {
|
||||
println!(concat!(
|
||||
$fixed, $msg
|
||||
), Utc::now().to_rfc3339_opts(SecondsFormat::Secs, false)$(, $($arg),*)?);
|
||||
};
|
||||
|
||||
(debug $msg: literal $(, $($arg: expr),*$(,)?)?) => {
|
||||
#[cfg(feature = "dev")]
|
||||
logger!(@inner "\x1b[38;5;244m[{}] \x1b[38;5;159mDEBUG \x1b[39m", $msg $(,$($arg),*)?);
|
||||
};
|
||||
|
||||
(info $msg: literal $(, $($arg: expr),*$(,)?)?) => {
|
||||
logger!(@inner "\x1b[38;5;244m[{}] \x1b[38;5;148mINFO \x1b[39m", $msg $(,$($arg),*)?);
|
||||
};
|
||||
|
||||
(warn $msg: literal $(, $($arg: expr),*$(,)?)?) => {
|
||||
logger!(@inner "\x1b[38;5;244m[{}] \x1b[38;5;214mWARNING \x1b[39m", $msg $(,$($arg),*)?);
|
||||
};
|
||||
|
||||
(err $msg: literal $(, $($arg: expr),*$(,)?)?) => {
|
||||
logger!(@inner "\x1b[38;5;244m[{}] \x1b[38;5;196mERROR \x1b[39m", $msg $(,$($arg),*)?);
|
||||
};
|
||||
}
|
152
src/main.rs
Normal file
152
src/main.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use std::sync::Arc;
|
||||
use chrono::{SecondsFormat, Utc};
|
||||
#[cfg(feature = "dev")]
|
||||
use {
|
||||
inferium::TokioInet,
|
||||
tokio::net::TcpListener,
|
||||
std::net::SocketAddr,
|
||||
};
|
||||
#[cfg(not(feature = "dev"))]
|
||||
use {
|
||||
inferium::TokioUnix,
|
||||
tokio::net::UnixListener,
|
||||
tokio::net::unix::SocketAddr,
|
||||
};
|
||||
use inferium::h1::AsyncServer;
|
||||
use tokio::sync::mpsc::{self, Sender};
|
||||
use std::sync::atomic::AtomicU32;
|
||||
|
||||
mod mime_types;
|
||||
mod response_builder;
|
||||
mod router;
|
||||
mod logger;
|
||||
|
||||
async fn client_handler(
|
||||
#[cfg(feature = "dev")]
|
||||
mut stream: AsyncServer<TokioInet>,
|
||||
#[cfg(not(feature = "dev"))]
|
||||
mut stream: AsyncServer<TokioUnix>,
|
||||
#[cfg(feature = "dev")]
|
||||
addr: SocketAddr,
|
||||
#[cfg(not(feature = "dev"))]
|
||||
_addr: SocketAddr,
|
||||
tx: Sender<()>,
|
||||
state: Arc<State>,
|
||||
) {
|
||||
logger!(debug "new connection from {:?}", addr);
|
||||
loop {
|
||||
// Receive request
|
||||
let Ok(req) = stream.receive_request().await else {
|
||||
logger!(debug "invalid request from {:?}", addr);
|
||||
break;
|
||||
};
|
||||
|
||||
// Handling logic
|
||||
let res = match router::handle_request(req, &tx, &state).await {
|
||||
Ok(v) => v,
|
||||
#[cfg(feature = "dev")]
|
||||
Err(e) => { logger!(err "internal server error: {:?}", e); break; }
|
||||
#[cfg(not(feature = "dev"))]
|
||||
Err(_) => { break; },
|
||||
};
|
||||
|
||||
// Send response
|
||||
match stream.send_response(&res.0).await {
|
||||
Ok(()) => {},
|
||||
#[cfg(feature = "dev")]
|
||||
Err(e) => { logger!(debug "err when sending response {:?}", e); break; },
|
||||
#[cfg(not(feature = "dev"))]
|
||||
Err(_) => { break; },
|
||||
}
|
||||
let Some(body) = res.1 else {
|
||||
continue;
|
||||
};
|
||||
match stream.send_body_bytes(&body).await {
|
||||
Ok(()) => {},
|
||||
#[cfg(feature = "dev")]
|
||||
Err(e) => { logger!(debug "err when sending response body {:?}", e); break; },
|
||||
#[cfg(not(feature = "dev"))]
|
||||
Err(_) => { break; },
|
||||
}
|
||||
}
|
||||
logger!(debug "end connection to {:?}", addr);
|
||||
}
|
||||
|
||||
async fn server_handler(
|
||||
#[cfg(feature = "dev")]
|
||||
listener: TcpListener,
|
||||
#[cfg(not(feature = "dev"))]
|
||||
listener: UnixListener,
|
||||
tx: Sender<()>,
|
||||
state: Arc<State>,
|
||||
) {
|
||||
loop {
|
||||
let Ok((conn, addr)) = listener.accept().await else {
|
||||
continue;
|
||||
};
|
||||
let tx = tx.clone();
|
||||
let state = state.clone();
|
||||
tokio::task::spawn(async move {
|
||||
#[cfg(feature = "dev")]
|
||||
client_handler(AsyncServer::<TokioInet>::new(TokioInet::new(conn)), addr, tx, state).await;
|
||||
#[cfg(not(feature = "dev"))]
|
||||
client_handler(AsyncServer::<TokioUnix>::new(TokioUnix::new(conn)), addr, tx, state).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
visit_count: AtomicU32,
|
||||
db: sled::Db,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(db_path: &str) -> Self {
|
||||
Self {
|
||||
visit_count: 0.into(),
|
||||
db: sled::open(db_path).expect("could not open the database")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Is used to cleanup old (non-active) unix socket before binding.
|
||||
///
|
||||
/// # Caution
|
||||
/// This function WILL panic if any I/O operation fails.
|
||||
#[cfg(not(feature = "dev"))]
|
||||
#[inline(always)]
|
||||
fn delete_if_exists(filepath: &str) {
|
||||
if std::fs::exists(filepath)
|
||||
.expect(&format!("could not get info for filepath {:?}", filepath)) {
|
||||
std::fs::remove_file(filepath)
|
||||
.expect(&format!("could not delete existing socket {:?}", filepath));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn main() {
|
||||
const BIND_TO: &str = std::env!("BIND_TO");
|
||||
const DB_PATH: &str = std::env!("DB_PATH");
|
||||
|
||||
|
||||
logger!(debug "binding to {:?}", BIND_TO);
|
||||
#[cfg(not(feature = "dev"))]
|
||||
delete_if_exists(BIND_TO);
|
||||
#[cfg(not(feature = "dev"))]
|
||||
let listener = UnixListener::bind(BIND_TO)
|
||||
.expect(&format!("could not bind to {:?}", BIND_TO));
|
||||
#[cfg(feature = "dev")]
|
||||
let listener = TcpListener::bind(BIND_TO).await
|
||||
.expect(&format!("could not bind to {:?}", BIND_TO));
|
||||
|
||||
let (tx, mut rx) = mpsc::channel::<()>(1);
|
||||
let state = Arc::new(State::new(DB_PATH));
|
||||
|
||||
logger!(info "server running on {:?}", BIND_TO);
|
||||
tokio::select!(
|
||||
_ = server_handler(listener, tx, state) => {}
|
||||
_ = rx.recv() => {},
|
||||
);
|
||||
|
||||
logger!(info "graceful shutdown");
|
||||
}
|
20
src/mime_types.rs
Normal file
20
src/mime_types.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
macro_rules! mimes {
|
||||
($($ident: ident = $value: literal),*$(,)?) => {
|
||||
pub enum MimeType {
|
||||
$($ident),*
|
||||
}
|
||||
|
||||
impl MimeType {
|
||||
pub fn text(&self) -> &'static str {
|
||||
match self { $(Self::$ident => $value),* }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mimes! {
|
||||
Txt = "text/plain",
|
||||
Html = "text/html",
|
||||
Css = "text/css",
|
||||
Js = "text/javascript",
|
||||
}
|
8
src/response_builder.rs
Normal file
8
src/response_builder.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#[macro_export]
|
||||
macro_rules! response {
|
||||
($proto: ident $status: ident, [$($hkey: ident : $hval: literal),*$(,)?]) => {
|
||||
ResponseHead::new(Status::$status, ProtocolVariant::$proto, HashMap::from([
|
||||
$((HeaderKey::$hkey, HeaderValue::new_unchecked($hval))),*
|
||||
]))
|
||||
};
|
||||
}
|
127
src/router.rs
Normal file
127
src/router.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use std::{collections::HashMap, sync::{atomic::Ordering, Arc}};
|
||||
use askama::Template;
|
||||
#[cfg(feature = "dev")]
|
||||
use inferium::TokioInet;
|
||||
#[cfg(not(feature = "dev"))]
|
||||
use inferium::TokioUnix;
|
||||
use inferium::{
|
||||
h1::{Request, ResponseHead, ProtocolVariant},
|
||||
Method, Status, HeaderKey, HeaderValue
|
||||
};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use chrono::{Utc, SecondsFormat};
|
||||
use crate::{logger, mime_types::MimeType, State};
|
||||
|
||||
macro_rules! response {
|
||||
(@add_default_headers $($entry: tt),*$(,)?) => {[
|
||||
(HeaderKey::SERVER, HeaderValue::new_unchecked("inferium".to_string())),
|
||||
(
|
||||
HeaderKey::OTHER("x-powered-by".to_string()),
|
||||
HeaderValue::new_unchecked("zumepro".to_string())
|
||||
),
|
||||
$($entry),*
|
||||
]};
|
||||
|
||||
(@log $head: ident, $status: ident) => {
|
||||
logger!(
|
||||
info "[{}] {} {:?} {}",
|
||||
Status::$status,
|
||||
$head.method(),
|
||||
$head.uri().to_string(),
|
||||
$head.proto(),
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
$head: ident,
|
||||
$proto: ident $status: ident,
|
||||
[$($hkey: ident : $hval: expr),*$(,)?],
|
||||
file $static_filename: literal $ctype: ident
|
||||
) => {{
|
||||
response!(@log $head, $status);
|
||||
const BODY: &[u8] = include_bytes!($static_filename);
|
||||
(ResponseHead::new(Status::$status, ProtocolVariant::$proto, HashMap::from(
|
||||
response!(@add_default_headers
|
||||
(HeaderKey::CONTENT_LENGTH, BODY.len().into()),
|
||||
(HeaderKey::CONTENT_TYPE, HeaderValue::new_unchecked(MimeType::$ctype.text().to_string())),
|
||||
$((HeaderKey::$hkey, HeaderValue::new_unchecked($hval.to_string()))),*
|
||||
))), Some(BODY.to_vec()))
|
||||
}};
|
||||
|
||||
(
|
||||
$head: ident,
|
||||
$proto: ident $status: ident,
|
||||
[$($hkey: ident : $hval: expr),*$(,)?],
|
||||
$body: expr
|
||||
) => {{
|
||||
response!(@log $head, $status);
|
||||
(ResponseHead::new(Status::$status, ProtocolVariant::$proto, HashMap::from(
|
||||
response!(@add_default_headers
|
||||
(HeaderKey::CONTENT_LENGTH, $body.len().into()),
|
||||
$((HeaderKey::$hkey, HeaderValue::new_unchecked($hval.to_string()))),*
|
||||
))), Some($body))
|
||||
}};
|
||||
|
||||
($head: ident, $proto: ident $status: ident, [$($hkey: ident : $hval: expr),*$(,)?]) => {{
|
||||
response!(@log $head, $status);
|
||||
(ResponseHead::new(Status::$status, ProtocolVariant::$proto, HashMap::from(
|
||||
response!(@add_default_headers
|
||||
$((HeaderKey::$hkey, HeaderValue::new_unchecked($hval.to_string()))),*
|
||||
))), None)
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InternalServerError {
|
||||
Templating(askama::Error),
|
||||
Generic(String),
|
||||
}
|
||||
|
||||
impl From<askama::Error> for InternalServerError {
|
||||
fn from(value: askama::Error) -> Self {
|
||||
Self::Templating(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a response for the given request
|
||||
///
|
||||
/// # Returns
|
||||
/// - Http head to send.
|
||||
/// - Optionally a body (note that this must be preceeded by putting `content-length` header in the
|
||||
/// response head).
|
||||
pub async fn handle_request<'a>(
|
||||
#[cfg(feature = "dev")]
|
||||
req: Request<'_, TokioInet>,
|
||||
#[cfg(not(feature = "dev"))]
|
||||
req: Request<'_, TokioUnix>,
|
||||
_tx: &'_ Sender<()>,
|
||||
state: &Arc<State>,
|
||||
) -> Result<(ResponseHead, Option<Vec<u8>>), InternalServerError> {
|
||||
let head = req.head();
|
||||
Ok(match (head.method(), head.uri().path()) {
|
||||
|
||||
(&Method::GET, "/") => {
|
||||
let counter = state.visit_count.fetch_add(1, Ordering::Relaxed);
|
||||
let response_body = Homepage { visit_count: counter }.render()?.into_bytes();
|
||||
response!(head, HTTP1_1 Ok, [CONTENT_TYPE: MimeType::Html.text()], response_body)
|
||||
},
|
||||
|
||||
(&Method::GET, "/style.css") => response!(
|
||||
head, HTTP1_1 Ok, [], file "../client/dst/style.css" Css
|
||||
),
|
||||
|
||||
(&Method::GET, "/script.js") => response!(
|
||||
head, HTTP1_1 Ok, [], file "../client/dst/script.js" Js
|
||||
),
|
||||
|
||||
_ => response!(head, HTTP1_1 NotFound, [
|
||||
CONTENT_TYPE: MimeType::Txt.text(),
|
||||
], b"Not Found. :(".to_vec()),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct Homepage {
|
||||
visit_count: u32,
|
||||
}
|
Reference in New Issue
Block a user