Compare commits
No commits in common. "ncurses" and "main" have entirely different histories.
794
Cargo.lock
generated
Normal file
794
Cargo.lock
generated
Normal file
@ -0,0 +1,794 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.21.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cassowary"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "castaway"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.90"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-eyre"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"color-spantrace",
|
||||||
|
"eyre",
|
||||||
|
"indenter",
|
||||||
|
"once_cell",
|
||||||
|
"owo-colors",
|
||||||
|
"tracing-error",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-spantrace"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"owo-colors",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-error",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compact_str"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
|
||||||
|
dependencies = [
|
||||||
|
"castaway",
|
||||||
|
"cfg-if",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"static_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.2",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "eyre"
|
||||||
|
version = "0.6.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
|
||||||
|
dependencies = [
|
||||||
|
"indenter",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.28.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"allocator-api2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indenter"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indoc"
|
||||||
|
version = "2.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.153"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "molehole"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"color-eyre",
|
||||||
|
"crossterm",
|
||||||
|
"eyre",
|
||||||
|
"ratatui",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.32.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "owo-colors"
|
||||||
|
version = "3.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||||
|
|
||||||
|
[[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.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.78"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ratatui"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bcb12f8fbf6c62614b0d56eb352af54f6a22410c3b079eb53ee93c7b97dd31d8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.2",
|
||||||
|
"cassowary",
|
||||||
|
"compact_str",
|
||||||
|
"crossterm",
|
||||||
|
"indoc",
|
||||||
|
"itertools",
|
||||||
|
"lru",
|
||||||
|
"paste",
|
||||||
|
"stability",
|
||||||
|
"strum",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stability"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn 2.0.52",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.52"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-error"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
||||||
|
dependencies = [
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||||
|
dependencies = [
|
||||||
|
"sharded-slab",
|
||||||
|
"thread_local",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[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-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 = "zerocopy"
|
||||||
|
version = "0.7.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.7.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.52",
|
||||||
|
]
|
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "molehole"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
color-eyre = "0.6.2"
|
||||||
|
crossterm = "0.27.0"
|
||||||
|
eyre = "0.6.12"
|
||||||
|
ratatui = "0.26.1"
|
||||||
|
url = "2.5.0"
|
32
Makefile
32
Makefile
@ -1,32 +0,0 @@
|
|||||||
NAME=molehole
|
|
||||||
|
|
||||||
SRCDIR=src
|
|
||||||
OBJDIR=obj
|
|
||||||
BUILDDIR=build
|
|
||||||
|
|
||||||
SOURCES=$(shell find $(SRCDIR) -type f -name '*.c')
|
|
||||||
OBJSOURCES=$(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(SOURCES)))
|
|
||||||
|
|
||||||
CC=clang
|
|
||||||
CFLAGS=-Wall -Wextra -pedantic -O0 -g
|
|
||||||
LDFLAGS=-lncurses -lssl -lc
|
|
||||||
INCLUDEFLAGS=-Iinclude
|
|
||||||
|
|
||||||
VPATH=$(dir $(SOURCES))
|
|
||||||
|
|
||||||
$(BUILDDIR)/$(NAME): $(OBJSOURCES) | $(BUILDDIR)
|
|
||||||
$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
|
|
||||||
|
|
||||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
|
||||||
$(CC) -c -o $@ $< $(CFLAGS) $(INCLUDEFLAGS)
|
|
||||||
|
|
||||||
$(BUILDDIR):
|
|
||||||
mkdir -p $(BUILDDIR)
|
|
||||||
$(OBJDIR):
|
|
||||||
mkdir -p $(OBJDIR)
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean:
|
|
||||||
rm -rfv $(OBJDIR)
|
|
||||||
rm -rfv $(BUILDDIR)
|
|
15
README
Executable file → Normal file
15
README
Executable file → Normal file
@ -1,13 +1,10 @@
|
|||||||
===
|
|
||||||
Molehole
|
Molehole
|
||||||
===
|
========
|
||||||
|
|
||||||
Molehole is a WIP client for the Molerat[0] protocol.
|
Molehole is a (WIP) client for the Molerat protocol [0].
|
||||||
It does not currently function, but it is being actively developed.
|
It is implemented in rust using ratatui [1].
|
||||||
|
|
||||||
TODO:
|
Contributions are welcome, please send them to molerat@git.trinket.icu
|
||||||
- Implement mtxt parser
|
|
||||||
- Implement display
|
|
||||||
- Implement response code handler
|
|
||||||
|
|
||||||
[0]: http://trkt.in/molerat
|
[0]: https://trkt.in/molerat
|
||||||
|
[1]: https://ratatui.rs
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
-Wall
|
|
||||||
-Wextra
|
|
||||||
-pedantic
|
|
||||||
|
|
||||||
-Iinclude
|
|
||||||
-I/nix/store/vgxb7a4zdrqv01wikdjz106v9rd8bnrd-openssl-3.0.13-dev/include
|
|
||||||
-I/nix/store/y7syj9lr1h9r2b1nx2pk9fdqyc76ag58-ncurses-6.4-dev/include
|
|
||||||
|
|
||||||
-lncurses
|
|
||||||
-lssl
|
|
@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1709237383,
|
"lastModified": 1709479366,
|
||||||
"narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
|
"narHash": "sha256-n6F0n8UV6lnTZbYPl1A9q1BS0p4hduAv1mGAP17CVd0=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
|
"rev": "b8697e57f10292a6165a20f03d2f42920dfaf973",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
37
flake.nix
37
flake.nix
@ -10,42 +10,17 @@
|
|||||||
system = "aarch64-darwin";
|
system = "aarch64-darwin";
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
in {
|
in {
|
||||||
packages.${system}.default =
|
|
||||||
pkgs.stdenv.mkDerivation {
|
|
||||||
name = "molehole";
|
|
||||||
src = ./.;
|
|
||||||
|
|
||||||
buildInputs = with pkgs; [ncurses openssl];
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/bin
|
|
||||||
mv build/molehole $out/bin
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
devShells.${system}.default =
|
devShells.${system}.default =
|
||||||
pkgs.mkShell {
|
pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
llvmPackages.clang
|
rustc
|
||||||
neovim
|
rustfmt
|
||||||
|
cargo
|
||||||
openssl.dev
|
rust-analyzer
|
||||||
ncurses.dev
|
libiconv
|
||||||
|
clippy
|
||||||
];
|
];
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
cat << EOF > compile_flags.txt
|
|
||||||
-Wall
|
|
||||||
-Wextra
|
|
||||||
-pedantic
|
|
||||||
|
|
||||||
-Iinclude
|
|
||||||
-I${pkgs.openssl.dev}/include
|
|
||||||
-I${pkgs.ncurses.dev}/include
|
|
||||||
|
|
||||||
-lncurses
|
|
||||||
-lssl
|
|
||||||
EOF
|
|
||||||
|
|
||||||
exec zsh
|
exec zsh
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
#ifndef _COLOR_H_
|
|
||||||
#define _COLOR_H_
|
|
||||||
#include <ncurses.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize color pairs for the rest of the application.
|
|
||||||
*/
|
|
||||||
void set_colors(void);
|
|
||||||
|
|
||||||
enum StatusColorPairs { STATUS_MAIN = 1, STATUS_ERROR, STATUS_PROMPT };
|
|
||||||
|
|
||||||
enum StatusColors {
|
|
||||||
COLOR_GREY = 238,
|
|
||||||
COLOR_DIM_WHITE = 250,
|
|
||||||
COLOR_DIM_RED = 160
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,56 +0,0 @@
|
|||||||
#ifndef _CONFIG_H_
|
|
||||||
#define _CONFIG_H_
|
|
||||||
#include <ncurses.h>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
|
|
||||||
#include "response.h"
|
|
||||||
#include "url.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Molehole internal setting.
|
|
||||||
*
|
|
||||||
* Holds important information for the molehole runtime like terminal
|
|
||||||
* dimensions, and active ncurses windows.
|
|
||||||
*/
|
|
||||||
struct config_internal {
|
|
||||||
int height;
|
|
||||||
int width;
|
|
||||||
|
|
||||||
WINDOW *page_win;
|
|
||||||
WINDOW *status_win;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Molehole current state information.
|
|
||||||
*
|
|
||||||
* Holds information pertaining to the content displayed such as the current
|
|
||||||
* page url.
|
|
||||||
*/
|
|
||||||
struct config_state {
|
|
||||||
char *url_string;
|
|
||||||
struct url *url;
|
|
||||||
struct connection *conn;
|
|
||||||
struct response *res;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Molehole configuration.
|
|
||||||
*
|
|
||||||
* Holds the state of the program including user configuration.
|
|
||||||
*/
|
|
||||||
struct config {
|
|
||||||
struct config_internal i;
|
|
||||||
struct config_state s;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a config with default parameters.
|
|
||||||
*/
|
|
||||||
void init_config(struct config *conf);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up a config state and internal.
|
|
||||||
*/
|
|
||||||
void conf_cleanup(struct config *conf);
|
|
||||||
#endif
|
|
@ -1,44 +0,0 @@
|
|||||||
#ifndef _CONNECT_H_
|
|
||||||
#define _CONNECT_H_
|
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "url.h"
|
|
||||||
|
|
||||||
/* Error codes */
|
|
||||||
enum ConnectError {
|
|
||||||
ERR_GETADDRINFO = -1,
|
|
||||||
ERR_SOCKET = -2,
|
|
||||||
ERR_CONNECT = -3,
|
|
||||||
ERR_SSL_CTX = -4,
|
|
||||||
ERR_SSL_SSL = -5,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds all needed information to manage an open TLS connection.
|
|
||||||
*/
|
|
||||||
struct connection {
|
|
||||||
SSL *ssl;
|
|
||||||
int sockfd;
|
|
||||||
bool used;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to molerat server.
|
|
||||||
*
|
|
||||||
* Sets `*ssl` in `struct config *conf` to the current active connection.
|
|
||||||
*/
|
|
||||||
int tls_connect(struct config *conf, struct url url);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnect and free open connection
|
|
||||||
*/
|
|
||||||
void tls_cleanup(struct connection *conn);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocate a new instance of a `struct connection`
|
|
||||||
*/
|
|
||||||
struct connection *init_connection(void);
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,9 +0,0 @@
|
|||||||
#ifndef _MOLERAT_H_
|
|
||||||
#define _MOLERAT_H_
|
|
||||||
|
|
||||||
struct key {
|
|
||||||
char* key;
|
|
||||||
char* value;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,24 +0,0 @@
|
|||||||
#ifndef _NET_H_
|
|
||||||
#define _NET_H_
|
|
||||||
#include "config.h"
|
|
||||||
#include "request.h"
|
|
||||||
#include "response.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a `struct request` to the current `struct connection` contained within
|
|
||||||
* `struct config *conf`.
|
|
||||||
*/
|
|
||||||
int send_request(struct config *conf, struct request *req);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a response from the current `struct connection` and parses it into
|
|
||||||
* `struct response *res`
|
|
||||||
*/
|
|
||||||
int read_response(struct config *conf, struct response *res);
|
|
||||||
|
|
||||||
enum NetError {
|
|
||||||
SSL_SEND_ERROR = -1,
|
|
||||||
ALLOC_ERROR = -2,
|
|
||||||
RESPONSE_PARSE_ERROR = -3,
|
|
||||||
};
|
|
||||||
#endif
|
|
@ -1,22 +0,0 @@
|
|||||||
#ifndef _REQUEST_H_
|
|
||||||
#define _REQUEST_H_
|
|
||||||
#include "molerat.h"
|
|
||||||
#include "url.h"
|
|
||||||
|
|
||||||
enum RequestKind { GET, PUT, DEL };
|
|
||||||
|
|
||||||
struct request {
|
|
||||||
enum RequestKind kind;
|
|
||||||
struct url url;
|
|
||||||
struct key *keys;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a `struct request` to a string of chars that can be sent over the
|
|
||||||
* network.
|
|
||||||
*
|
|
||||||
* WARNING: Printing the screen won't work because the request ends in two
|
|
||||||
* \r\n's
|
|
||||||
*/
|
|
||||||
char *request_to_string(struct request *request);
|
|
||||||
#endif
|
|
@ -1,50 +0,0 @@
|
|||||||
#ifndef _RESPONSE_H_
|
|
||||||
#define _RESPONSE_H_
|
|
||||||
|
|
||||||
enum ResponseStatus {
|
|
||||||
SUCCESS = 10,
|
|
||||||
CONTENT_UNCHANGED = 11,
|
|
||||||
|
|
||||||
PERMANENT_REDIRECT = 20,
|
|
||||||
TEMPORARY_REDIRECT = 21,
|
|
||||||
|
|
||||||
MALFORMED_REQUEST = 30,
|
|
||||||
INVALID_REQUEST = 31,
|
|
||||||
NOT_AVAILABLE = 32,
|
|
||||||
|
|
||||||
INTERNAL_ERROR = 40,
|
|
||||||
NOT_SUPPORTED = 41,
|
|
||||||
SLOW_DOWN = 42,
|
|
||||||
|
|
||||||
CLIENT_CERTIFICATE_REQUIRED = 50
|
|
||||||
};
|
|
||||||
|
|
||||||
struct mime_type {
|
|
||||||
char *type;
|
|
||||||
char *subtype;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct response {
|
|
||||||
enum ResponseStatus status;
|
|
||||||
char *message;
|
|
||||||
struct mime_type type;
|
|
||||||
unsigned int length;
|
|
||||||
char *hash;
|
|
||||||
char *content;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a string into a `struct response`.
|
|
||||||
*/
|
|
||||||
int parse_response(struct response *res, char *s);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deallocates `struct response *res`.
|
|
||||||
*/
|
|
||||||
void free_response(struct response *res);
|
|
||||||
|
|
||||||
enum ResponseError {
|
|
||||||
INVALID_STATUS = -1,
|
|
||||||
UNKNOWN_KEY = -2,
|
|
||||||
};
|
|
||||||
#endif
|
|
@ -1,27 +0,0 @@
|
|||||||
#ifndef _STATUS_H_
|
|
||||||
#define _STATUS_H_
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocate a status window at the bottom of the terminal
|
|
||||||
*
|
|
||||||
* Saves the status window into the `*conf` parameter.
|
|
||||||
*/
|
|
||||||
void init_status(struct config *conf);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the message currently displayed in the status window.
|
|
||||||
*/
|
|
||||||
void update_status(struct config *conf, char *s);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the status to prompt for a Molerat URL. The raw string recieved will be.
|
|
||||||
* placed in `*conf`
|
|
||||||
*/
|
|
||||||
void prompt_status_url(struct config *conf);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as `update_status` but signify error.
|
|
||||||
*/
|
|
||||||
void error_status(struct config *conf, char *s);
|
|
||||||
#endif
|
|
@ -1,42 +0,0 @@
|
|||||||
#ifndef _URL_H_
|
|
||||||
#define _URL_H_
|
|
||||||
|
|
||||||
#define MAX_URL_LENGTH 2048
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Molerat URL.
|
|
||||||
*
|
|
||||||
* Holds all relevant for molehole to make requests.
|
|
||||||
*/
|
|
||||||
struct url {
|
|
||||||
char *scheme;
|
|
||||||
char *host;
|
|
||||||
int port;
|
|
||||||
char *path;
|
|
||||||
char *query;
|
|
||||||
char *fragment;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a `*s` into `*url` as a molerat URL.
|
|
||||||
*/
|
|
||||||
int parse_url(struct url *url, char *s);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deallocates `struct url *url`.
|
|
||||||
*/
|
|
||||||
void free_url(struct url *url);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a `struct url*` with sensible defaults.
|
|
||||||
*/
|
|
||||||
struct url *init_url(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get's the strlen of a `struct url*`
|
|
||||||
*/
|
|
||||||
int len_url(struct url *url);
|
|
||||||
|
|
||||||
/* Error codes */
|
|
||||||
enum UrlError { INVALID_CHARACTER = -1, MISSING_HOST = -2, MISSING_PORT = -3 };
|
|
||||||
#endif
|
|
@ -1,11 +0,0 @@
|
|||||||
#ifndef _UTIL_H_
|
|
||||||
#define _UTIL_H_
|
|
||||||
#include <stdnoreturn.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kill program by printing message `*s` and errno.
|
|
||||||
*
|
|
||||||
* Shuts down ncurses before killing program.
|
|
||||||
*/
|
|
||||||
noreturn void die(const char *s);
|
|
||||||
#endif
|
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
max_width = 80
|
154
src/app.rs
Normal file
154
src/app.rs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
use crossterm::event::Event;
|
||||||
|
use eyre::Result;
|
||||||
|
use ratatui::prelude::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::app_action::AppAction;
|
||||||
|
use crate::app_event::AppEvent;
|
||||||
|
use crate::component::Component;
|
||||||
|
use crate::components;
|
||||||
|
use crate::keys::key_commands::KeyCommand;
|
||||||
|
use crate::tui;
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
pub tui: tui::Tui,
|
||||||
|
pub tick_rate: Duration,
|
||||||
|
pub components: Vec<Box<dyn Component>>,
|
||||||
|
pub key_commands: Vec<KeyCommand>,
|
||||||
|
|
||||||
|
should_quit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new(tick_rate: Duration) -> Result<Self> {
|
||||||
|
let tui = tui::init()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
tui,
|
||||||
|
tick_rate,
|
||||||
|
|
||||||
|
should_quit: false,
|
||||||
|
components: vec![],
|
||||||
|
key_commands: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> Result<()> {
|
||||||
|
let global_keys = components::global_keys::GlobalKeys {
|
||||||
|
key_commands: self.key_commands.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let status_bar = components::status::StatusBar {
|
||||||
|
message: "Press '?' to show the help menu".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let url_manager = components::url_manager::UrlManager::default();
|
||||||
|
self.components = vec![
|
||||||
|
Box::new(global_keys),
|
||||||
|
Box::new(status_bar),
|
||||||
|
Box::new(url_manager),
|
||||||
|
];
|
||||||
|
|
||||||
|
for component in &mut self.components {
|
||||||
|
component.init()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if self.should_quit {
|
||||||
|
break Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.draw()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&mut self) -> Result<()> {
|
||||||
|
let event: Option<AppEvent> = match tui::get_event(self.tick_rate)? {
|
||||||
|
Some(event) => match event {
|
||||||
|
Event::Key(key) => Some(AppEvent::Key(key)),
|
||||||
|
Event::Mouse(mouse) => Some(AppEvent::Mouse(mouse)),
|
||||||
|
Event::FocusGained => todo!(),
|
||||||
|
Event::FocusLost => todo!(),
|
||||||
|
Event::Paste(_) => todo!(),
|
||||||
|
Event::Resize(_, _) => todo!(),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(event) = event {
|
||||||
|
self.handle_event(event)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.should_quit {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tui.draw(|frame| {
|
||||||
|
let layout = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(vec![
|
||||||
|
Constraint::Percentage(100),
|
||||||
|
Constraint::Min(1),
|
||||||
|
])
|
||||||
|
.split(frame.size());
|
||||||
|
|
||||||
|
// status bar
|
||||||
|
let _ = self.components[1].render(frame, layout[1]);
|
||||||
|
|
||||||
|
// global_keys
|
||||||
|
let _ = self.components[0].render(frame, frame.size());
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.update()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) -> Result<()> {
|
||||||
|
let mut events: Vec<AppEvent> = vec![];
|
||||||
|
for component in &mut self.components {
|
||||||
|
if let Some(event) = component.update() {
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
self.handle_event(event)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quit(&mut self) -> Result<()> {
|
||||||
|
tui::restore()?;
|
||||||
|
self.should_quit = true;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_action(&mut self, action: AppAction) -> Result<()> {
|
||||||
|
match action {
|
||||||
|
AppAction::Quit => Ok(self.quit()?),
|
||||||
|
_ => {
|
||||||
|
for component in &mut self.components {
|
||||||
|
component.handle_action(action.clone());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&mut self, event: AppEvent) -> Result<()> {
|
||||||
|
let mut actions: Vec<AppAction> = vec![];
|
||||||
|
for component in &mut self.components {
|
||||||
|
if let Some(action) = component.handle_event(event.clone()) {
|
||||||
|
actions.push(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for action in actions {
|
||||||
|
self.handle_action(action)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
24
src/app_action.rs
Normal file
24
src/app_action.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Debug)]
|
||||||
|
pub enum AppAction {
|
||||||
|
StatusBarSetMessage(String),
|
||||||
|
StatusBarSetError(String),
|
||||||
|
OpenUrl,
|
||||||
|
|
||||||
|
ScrollUp,
|
||||||
|
ScrollDown,
|
||||||
|
ScrollTop,
|
||||||
|
ScrollBottom,
|
||||||
|
|
||||||
|
ShowHelpMenu,
|
||||||
|
|
||||||
|
#[default]
|
||||||
|
Quit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AppAction {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
10
src/app_event.rs
Normal file
10
src/app_event.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use crossterm::event::{KeyEvent, MouseEvent};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum AppEvent {
|
||||||
|
Key(KeyEvent),
|
||||||
|
Mouse(MouseEvent),
|
||||||
|
|
||||||
|
OpenUrl(Url),
|
||||||
|
}
|
43
src/component.rs
Normal file
43
src/component.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use crossterm::event::{KeyEvent, MouseEvent};
|
||||||
|
use eyre::Result;
|
||||||
|
use ratatui::prelude::{Frame, Rect};
|
||||||
|
|
||||||
|
use crate::app_action::AppAction;
|
||||||
|
use crate::app_event::AppEvent;
|
||||||
|
|
||||||
|
pub trait Component {
|
||||||
|
fn init(&mut self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn handle_action(&mut self, action: AppAction) {}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn handle_event(&mut self, event: AppEvent) -> Option<AppAction> {
|
||||||
|
match event {
|
||||||
|
AppEvent::Key(key_event) => self.handle_key_event(key_event),
|
||||||
|
AppEvent::Mouse(mouse_event) => {
|
||||||
|
self.handle_mouse_event(mouse_event)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn handle_key_event(&mut self, key: KeyEvent) -> Option<AppAction> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Option<AppAction> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn update(&mut self) -> Option<AppEvent> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, frame: &mut Frame, rect: Rect) -> Result<()>;
|
||||||
|
}
|
131
src/components/global_keys.rs
Normal file
131
src/components/global_keys.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use crossterm::event::{KeyEvent, KeyEventKind};
|
||||||
|
use ratatui::prelude::{
|
||||||
|
Alignment, Color, Constraint, Direction, Frame, Layout, Line, Margin, Rect,
|
||||||
|
Span, Style, Stylize,
|
||||||
|
};
|
||||||
|
use ratatui::widgets::block::{Block, BorderType, Title};
|
||||||
|
use ratatui::widgets::{
|
||||||
|
Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||||
|
Wrap,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app_action::AppAction;
|
||||||
|
use crate::component::Component;
|
||||||
|
use crate::keys::key_commands::{serialize_key_event, KeyCommand};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GlobalKeys {
|
||||||
|
pub key_commands: Vec<KeyCommand>,
|
||||||
|
|
||||||
|
pub should_show: bool,
|
||||||
|
pub scroll: usize,
|
||||||
|
pub scroll_state: ScrollbarState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for GlobalKeys {
|
||||||
|
fn init(&mut self) -> eyre::Result<()> {
|
||||||
|
self.scroll_state =
|
||||||
|
ScrollbarState::new(self.key_commands.len()).position(self.scroll);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_action(&mut self, action: AppAction) {
|
||||||
|
match action {
|
||||||
|
AppAction::ScrollUp => {
|
||||||
|
if self.scroll > 0 {
|
||||||
|
self.scroll -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppAction::ScrollDown => {
|
||||||
|
if self.scroll < self.key_commands.len() - 1 {
|
||||||
|
self.scroll += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppAction::ScrollTop => {
|
||||||
|
self.scroll = 0;
|
||||||
|
}
|
||||||
|
AppAction::ScrollBottom => {
|
||||||
|
self.scroll = self.key_commands.len() - 1;
|
||||||
|
}
|
||||||
|
AppAction::ShowHelpMenu => {
|
||||||
|
self.should_show = !self.should_show;
|
||||||
|
self.scroll = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
self.scroll_state = self.scroll_state.position(self.scroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key_event(&mut self, key: KeyEvent) -> Option<AppAction> {
|
||||||
|
if key.kind == KeyEventKind::Press {
|
||||||
|
let key_event = serialize_key_event(key);
|
||||||
|
for key_command in &mut self.key_commands {
|
||||||
|
if key_command.key_code == key_event {
|
||||||
|
return Some(key_command.action.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> {
|
||||||
|
let vertical_center = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(50 / 2),
|
||||||
|
Constraint::Percentage(50),
|
||||||
|
Constraint::Percentage(50 / 2),
|
||||||
|
])
|
||||||
|
.split(rect);
|
||||||
|
let center = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(50 / 2),
|
||||||
|
Constraint::Percentage(50),
|
||||||
|
Constraint::Percentage(50 / 2),
|
||||||
|
])
|
||||||
|
.split(vertical_center[1])[1];
|
||||||
|
|
||||||
|
let block = Block::default()
|
||||||
|
.title(
|
||||||
|
Title::from("Keyboard shortcuts").alignment(Alignment::Center),
|
||||||
|
)
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Thick)
|
||||||
|
.style(Style::default().bg(Color::DarkGray));
|
||||||
|
|
||||||
|
let mut lines: Vec<Line> = vec![];
|
||||||
|
for key_command in &mut self.key_commands {
|
||||||
|
let command = Span::from(key_command.key_code.clone());
|
||||||
|
let description =
|
||||||
|
Span::from(key_command.description.clone()).italic();
|
||||||
|
let spacer = Span::from(" ");
|
||||||
|
|
||||||
|
let line = Line::from(vec![command, spacer, description]);
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
let commands = Paragraph::new(lines)
|
||||||
|
.block(block)
|
||||||
|
.wrap(Wrap { trim: true })
|
||||||
|
.scroll((u16::try_from(self.scroll)?, 0));
|
||||||
|
|
||||||
|
if self.should_show {
|
||||||
|
frame.render_widget(Clear, center);
|
||||||
|
frame.render_widget(commands, center);
|
||||||
|
frame.render_stateful_widget(
|
||||||
|
Scrollbar::new(ScrollbarOrientation::VerticalRight),
|
||||||
|
center.inner(&Margin {
|
||||||
|
vertical: 1,
|
||||||
|
horizontal: 0,
|
||||||
|
}),
|
||||||
|
&mut self.scroll_state,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
3
src/components/mod.rs
Normal file
3
src/components/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod global_keys;
|
||||||
|
pub mod status;
|
||||||
|
pub mod url_manager;
|
86
src/components/status.rs
Normal file
86
src/components/status.rs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
use ratatui::prelude::*;
|
||||||
|
use ratatui::widgets::*;
|
||||||
|
|
||||||
|
use crate::app_action::AppAction;
|
||||||
|
use crate::app_event::AppEvent;
|
||||||
|
use crate::component::Component;
|
||||||
|
use crate::keys::key_commands::serialize_key_event;
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct StatusBar {
|
||||||
|
pub message: String,
|
||||||
|
pub current_key: String,
|
||||||
|
pub error: bool,
|
||||||
|
pub url_to_open: Option<url::Url>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for StatusBar {
|
||||||
|
fn handle_key_event(
|
||||||
|
&mut self,
|
||||||
|
key: crossterm::event::KeyEvent,
|
||||||
|
) -> Option<AppAction> {
|
||||||
|
let key_str = serialize_key_event(key);
|
||||||
|
self.current_key = key_str;
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_action(&mut self, action: crate::app_action::AppAction) {
|
||||||
|
match action.clone() {
|
||||||
|
AppAction::StatusBarSetMessage(message) => {
|
||||||
|
self.error = false;
|
||||||
|
self.message = message;
|
||||||
|
}
|
||||||
|
AppAction::StatusBarSetError(message) => {
|
||||||
|
self.error = true;
|
||||||
|
self.message = message;
|
||||||
|
}
|
||||||
|
AppAction::OpenUrl => {
|
||||||
|
self.url_to_open =
|
||||||
|
Some(url::Url::parse("molerat://example.com").unwrap());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self) -> Option<AppEvent> {
|
||||||
|
if let Some(url) = &self.url_to_open {
|
||||||
|
let event = AppEvent::OpenUrl(url.clone());
|
||||||
|
self.url_to_open = None;
|
||||||
|
return Some(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
frame: &mut ratatui::prelude::Frame,
|
||||||
|
rect: ratatui::prelude::Rect,
|
||||||
|
) -> eyre::Result<()> {
|
||||||
|
let block =
|
||||||
|
Block::default().style(Style::default().bg(if self.error {
|
||||||
|
Color::Red
|
||||||
|
} else {
|
||||||
|
Color::DarkGray
|
||||||
|
}));
|
||||||
|
|
||||||
|
let layout = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(vec![
|
||||||
|
Constraint::Percentage(50),
|
||||||
|
Constraint::Percentage(50),
|
||||||
|
])
|
||||||
|
.split(rect);
|
||||||
|
|
||||||
|
let message = Paragraph::new(self.message.clone()).block(block.clone());
|
||||||
|
let current_key = Paragraph::new(self.current_key.clone())
|
||||||
|
.block(block)
|
||||||
|
.alignment(Alignment::Right);
|
||||||
|
|
||||||
|
frame.render_widget(message, layout[0]);
|
||||||
|
frame.render_widget(current_key, layout[1]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
34
src/components/url_manager.rs
Normal file
34
src/components/url_manager.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::app_action::AppAction;
|
||||||
|
use crate::app_event::AppEvent;
|
||||||
|
use crate::component::Component;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct UrlManager {
|
||||||
|
url: Option<Url>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for UrlManager {
|
||||||
|
fn handle_event(&mut self, event: AppEvent) -> Option<AppAction> {
|
||||||
|
match event {
|
||||||
|
AppEvent::OpenUrl(url) => {
|
||||||
|
self.url = Some(url.clone());
|
||||||
|
return Some(AppAction::StatusBarSetMessage(format!(
|
||||||
|
"Opening {}",
|
||||||
|
url.as_str()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
_frame: &mut ratatui::prelude::Frame,
|
||||||
|
_rect: ratatui::prelude::Rect,
|
||||||
|
) -> eyre::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
18
src/config.c
18
src/config.c
@ -1,18 +0,0 @@
|
|||||||
#include "config.h"
|
|
||||||
#include "connect.h"
|
|
||||||
#include "response.h"
|
|
||||||
|
|
||||||
void init_config(struct config *conf) {
|
|
||||||
conf->s.url = NULL;
|
|
||||||
conf->s.conn = NULL;
|
|
||||||
conf->s.res = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void conf_cleanup(struct config *conf) {
|
|
||||||
if (conf->s.url != NULL)
|
|
||||||
free_url(conf->s.url);
|
|
||||||
if (conf->s.conn != NULL)
|
|
||||||
tls_cleanup(conf->s.conn);
|
|
||||||
if (conf->s.res != NULL)
|
|
||||||
free_response(conf->s.res);
|
|
||||||
}
|
|
55
src/keys/key_commands.rs
Normal file
55
src/keys/key_commands.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
|
use crate::app_action::AppAction;
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct KeyCommand {
|
||||||
|
pub key_code: String,
|
||||||
|
pub description: String,
|
||||||
|
pub action: AppAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for KeyCommand {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}\t{}", self.key_code, self.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize_key_event(event: KeyEvent) -> String {
|
||||||
|
let mut modifiers = Vec::with_capacity(3);
|
||||||
|
if event.modifiers.intersects(KeyModifiers::CONTROL) {
|
||||||
|
modifiers.push("ctrl");
|
||||||
|
}
|
||||||
|
if event.modifiers.intersects(KeyModifiers::SUPER)
|
||||||
|
|| event.modifiers.intersects(KeyModifiers::HYPER)
|
||||||
|
|| event.modifiers.intersects(KeyModifiers::META)
|
||||||
|
{
|
||||||
|
modifiers.push("super");
|
||||||
|
}
|
||||||
|
if event.modifiers.intersects(KeyModifiers::ALT) {
|
||||||
|
modifiers.push("alt");
|
||||||
|
}
|
||||||
|
|
||||||
|
let char;
|
||||||
|
let key = match event.code {
|
||||||
|
KeyCode::Backspace | KeyCode::Delete => "del",
|
||||||
|
KeyCode::Enter => "enter",
|
||||||
|
KeyCode::Left => "left",
|
||||||
|
KeyCode::Right => "right",
|
||||||
|
KeyCode::Up => "up",
|
||||||
|
KeyCode::Down => "down",
|
||||||
|
KeyCode::Tab => "tab",
|
||||||
|
KeyCode::Char(' ') => "space",
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
char = c.to_string();
|
||||||
|
&char
|
||||||
|
}
|
||||||
|
KeyCode::Esc => "esc",
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
let separator = if modifiers.is_empty() { "" } else { "-" };
|
||||||
|
let serialized_event =
|
||||||
|
format!("{}{}{}", modifiers.join("-"), separator, key);
|
||||||
|
|
||||||
|
serialized_event
|
||||||
|
}
|
1
src/keys/mod.rs
Normal file
1
src/keys/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod key_commands;
|
83
src/main.c
83
src/main.c
@ -1,83 +0,0 @@
|
|||||||
#include <ncurses.h>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include "color.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "connect.h"
|
|
||||||
#include "net.h"
|
|
||||||
#include "request.h"
|
|
||||||
#include "response.h"
|
|
||||||
#include "status.h"
|
|
||||||
#include "url.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
void exit_cleanup(void) { endwin(); }
|
|
||||||
|
|
||||||
void init_ncurses(void) {
|
|
||||||
initscr();
|
|
||||||
cbreak();
|
|
||||||
noecho();
|
|
||||||
|
|
||||||
curs_set(0);
|
|
||||||
keypad(stdscr, TRUE);
|
|
||||||
scrollok(stdscr, TRUE);
|
|
||||||
|
|
||||||
if (!has_colors())
|
|
||||||
die("Terminal does not support colors");
|
|
||||||
|
|
||||||
start_color();
|
|
||||||
set_colors();
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
struct config conf;
|
|
||||||
init_config(&conf);
|
|
||||||
|
|
||||||
init_ncurses();
|
|
||||||
atexit(exit_cleanup);
|
|
||||||
|
|
||||||
getmaxyx(stdscr, conf.i.height, conf.i.width);
|
|
||||||
|
|
||||||
init_status(&conf);
|
|
||||||
conf.s.url = init_url();
|
|
||||||
conf.s.conn = init_connection();
|
|
||||||
|
|
||||||
prompt_status_url(&conf);
|
|
||||||
update_status(&conf, conf.s.url_string);
|
|
||||||
|
|
||||||
int rc = parse_url(conf.s.url, conf.s.url_string);
|
|
||||||
if (rc < 0) {
|
|
||||||
error_status(&conf, "Invalid URL");
|
|
||||||
conf_cleanup(&conf);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = tls_connect(&conf, *conf.s.url);
|
|
||||||
if (rc < 0) {
|
|
||||||
conf_cleanup(&conf);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct request req;
|
|
||||||
req.kind = GET;
|
|
||||||
req.url = *conf.s.url;
|
|
||||||
send_request(&conf, &req);
|
|
||||||
|
|
||||||
struct response res;
|
|
||||||
rc = read_response(&conf, &res);
|
|
||||||
if (rc < 0) {
|
|
||||||
conf_cleanup(&conf);
|
|
||||||
return rc * -1;
|
|
||||||
}
|
|
||||||
conf.s.res = &res;
|
|
||||||
|
|
||||||
printw("%s\n", conf.s.res->content);
|
|
||||||
|
|
||||||
getch();
|
|
||||||
|
|
||||||
conf_cleanup(&conf);
|
|
||||||
return 0;
|
|
||||||
}
|
|
59
src/main.rs
Normal file
59
src/main.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
mod app;
|
||||||
|
mod app_action;
|
||||||
|
mod app_event;
|
||||||
|
mod component;
|
||||||
|
mod components;
|
||||||
|
mod keys;
|
||||||
|
mod tui;
|
||||||
|
|
||||||
|
use eyre::Result;
|
||||||
|
|
||||||
|
use app_action::AppAction;
|
||||||
|
use keys::key_commands::KeyCommand;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tui::install_hooks()?;
|
||||||
|
let mut app = app::App::new(std::time::Duration::from_millis(10))?;
|
||||||
|
let mut key_commands = vec![
|
||||||
|
// Status bar
|
||||||
|
KeyCommand {
|
||||||
|
key_code: "o".to_string(),
|
||||||
|
description: "Open new link".to_string(),
|
||||||
|
action: AppAction::OpenUrl,
|
||||||
|
},
|
||||||
|
// Navigation
|
||||||
|
KeyCommand {
|
||||||
|
key_code: "g".to_string(),
|
||||||
|
description: "Scroll to top".to_string(),
|
||||||
|
action: AppAction::ScrollTop,
|
||||||
|
},
|
||||||
|
KeyCommand {
|
||||||
|
key_code: "G".to_string(),
|
||||||
|
description: "Scroll to bottom".to_string(),
|
||||||
|
action: AppAction::ScrollBottom,
|
||||||
|
},
|
||||||
|
KeyCommand {
|
||||||
|
key_code: "k".to_string(),
|
||||||
|
description: "Scroll up one line".to_string(),
|
||||||
|
action: AppAction::ScrollUp,
|
||||||
|
},
|
||||||
|
KeyCommand {
|
||||||
|
key_code: "j".to_string(),
|
||||||
|
description: "Scroll down one line".to_string(),
|
||||||
|
action: AppAction::ScrollDown,
|
||||||
|
},
|
||||||
|
KeyCommand {
|
||||||
|
key_code: "q".to_string(),
|
||||||
|
description: "Quit molehole".to_string(),
|
||||||
|
action: AppAction::Quit,
|
||||||
|
},
|
||||||
|
KeyCommand {
|
||||||
|
key_code: "?".to_string(),
|
||||||
|
description: "Toggle help menu".to_string(),
|
||||||
|
action: AppAction::ShowHelpMenu,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
app.key_commands.append(&mut key_commands);
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
}
|
@ -1,133 +0,0 @@
|
|||||||
#include <errno.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "connect.h"
|
|
||||||
#include "status.h"
|
|
||||||
#include "url.h"
|
|
||||||
|
|
||||||
int connect_socket(struct config *conf, struct url url) {
|
|
||||||
struct addrinfo hints;
|
|
||||||
struct addrinfo *servinfo;
|
|
||||||
|
|
||||||
memset(&hints, 0, sizeof hints);
|
|
||||||
hints.ai_family = AF_UNSPEC;
|
|
||||||
hints.ai_socktype = SOCK_STREAM;
|
|
||||||
hints.ai_flags = AI_PASSIVE;
|
|
||||||
|
|
||||||
char port[5];
|
|
||||||
sprintf(port, "%d", url.port);
|
|
||||||
int rc = getaddrinfo(url.host, port, &hints, &servinfo);
|
|
||||||
if (rc != 0) {
|
|
||||||
error_status(conf, (char *)gai_strerror(rc));
|
|
||||||
return ERR_GETADDRINFO;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct addrinfo *p;
|
|
||||||
int sockfd;
|
|
||||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
|
||||||
sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
|
||||||
if (sockfd == -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = connect(sockfd, p->ai_addr, p->ai_addrlen);
|
|
||||||
if (rc == -1) {
|
|
||||||
close(sockfd);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p == NULL) {
|
|
||||||
error_status(conf, strerror(errno));
|
|
||||||
return ERR_CONNECT;
|
|
||||||
}
|
|
||||||
update_status(conf, "Connected to socket");
|
|
||||||
freeaddrinfo(servinfo);
|
|
||||||
|
|
||||||
return sockfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
int tls_connect(struct config *conf, struct url url) {
|
|
||||||
char status_msg[strlen(url.host) + 128];
|
|
||||||
sprintf(status_msg, "Connecting to %s...", url.host);
|
|
||||||
update_status(conf, status_msg);
|
|
||||||
|
|
||||||
struct connection *conn = init_connection();
|
|
||||||
conf->s.conn = conn;
|
|
||||||
|
|
||||||
int sock;
|
|
||||||
int rc = connect_socket(conf, url);
|
|
||||||
if (rc < 0) {
|
|
||||||
return rc;
|
|
||||||
} else {
|
|
||||||
sock = rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SSL_METHOD *method;
|
|
||||||
method = TLS_method();
|
|
||||||
|
|
||||||
SSL_CTX *ctx;
|
|
||||||
ctx = SSL_CTX_new(method);
|
|
||||||
if (ctx == NULL) {
|
|
||||||
error_status(conf, "Failed to init SSL");
|
|
||||||
return ERR_SSL_CTX;
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL *ssl;
|
|
||||||
ssl = SSL_new(ctx);
|
|
||||||
if (ssl == NULL) {
|
|
||||||
error_status(conf, "Failed to init SSL");
|
|
||||||
return ERR_SSL_SSL;
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = SSL_set_fd(ssl, sock);
|
|
||||||
if (rc == 0) {
|
|
||||||
error_status(conf, "Failed to wrap socket");
|
|
||||||
return ERR_SSL_SSL;
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = SSL_connect(ssl);
|
|
||||||
if (rc <= 0) {
|
|
||||||
error_status(conf, "Failed to connect SSL");
|
|
||||||
return ERR_SSL_SSL;
|
|
||||||
}
|
|
||||||
|
|
||||||
conn->ssl = ssl;
|
|
||||||
conn->sockfd = sock;
|
|
||||||
conn->used = true;
|
|
||||||
conf->s.conn = conn;
|
|
||||||
|
|
||||||
update_status(conf, "Connected");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tls_cleanup(struct connection *conn) {
|
|
||||||
if (conn->sockfd) {
|
|
||||||
shutdown(conn->sockfd, SHUT_RDWR);
|
|
||||||
close(conn->sockfd);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conn->ssl) {
|
|
||||||
SSL_shutdown(conn->ssl);
|
|
||||||
SSL_CTX_free(SSL_get_SSL_CTX(conn->ssl));
|
|
||||||
SSL_free(conn->ssl);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct connection *init_connection(void) {
|
|
||||||
struct connection *conn = malloc(sizeof(struct connection));
|
|
||||||
memset(conn, 0, sizeof(struct connection));
|
|
||||||
|
|
||||||
return conn;
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
#include <openssl/ssl.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "connect.h"
|
|
||||||
#include "net.h"
|
|
||||||
#include "request.h"
|
|
||||||
#include "response.h"
|
|
||||||
#include "status.h"
|
|
||||||
|
|
||||||
int send_request(struct config *conf, struct request *req) {
|
|
||||||
char *req_string = request_to_string(req);
|
|
||||||
|
|
||||||
int rc = SSL_write(conf->s.conn->ssl, req_string, strlen(req_string));
|
|
||||||
|
|
||||||
if (rc < 0) {
|
|
||||||
return SSL_SEND_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(req_string);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int read_response(struct config *conf, struct response *res) {
|
|
||||||
struct timespec start_time;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
|
||||||
|
|
||||||
int buf_len = 4096;
|
|
||||||
char *buf = malloc(buf_len);
|
|
||||||
|
|
||||||
int bytes_read = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
bytes_read = SSL_read(conf->s.conn->ssl, buf, buf_len);
|
|
||||||
|
|
||||||
if (bytes_read == buf_len) {
|
|
||||||
buf_len *= 2;
|
|
||||||
char *temp_buf = realloc(buf, buf_len);
|
|
||||||
if (temp_buf == NULL)
|
|
||||||
return ALLOC_ERROR;
|
|
||||||
|
|
||||||
buf = temp_buf;
|
|
||||||
}
|
|
||||||
} while (bytes_read > 0);
|
|
||||||
|
|
||||||
int rc = parse_response(res, buf);
|
|
||||||
if (rc < 0) {
|
|
||||||
return RESPONSE_PARSE_ERROR;
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
|
|
||||||
struct timespec end_time;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
|
||||||
|
|
||||||
float time_diff = (end_time.tv_sec - start_time.tv_sec) +
|
|
||||||
1e-9 * (end_time.tv_nsec - start_time.tv_nsec);
|
|
||||||
|
|
||||||
int msg_len =
|
|
||||||
snprintf(NULL, 0, "Received after %0.3f seconds", time_diff) + 1;
|
|
||||||
char *msg = malloc(msg_len);
|
|
||||||
snprintf(msg, msg_len, "Received after %.3f seconds", time_diff);
|
|
||||||
|
|
||||||
update_status(conf, msg);
|
|
||||||
free(msg);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
#include <ncurses.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "request.h"
|
|
||||||
#include "url.h"
|
|
||||||
|
|
||||||
char *get_request_kind(enum RequestKind kind) {
|
|
||||||
switch (kind) {
|
|
||||||
case GET:
|
|
||||||
return "get";
|
|
||||||
case PUT:
|
|
||||||
return "put";
|
|
||||||
case DEL:
|
|
||||||
return "del";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *request_to_string(struct request *req) {
|
|
||||||
int len = sizeof(struct request) +
|
|
||||||
6; // +1 for null terminator +5 for request whitespace
|
|
||||||
|
|
||||||
char *buf = malloc(len);
|
|
||||||
|
|
||||||
char *kind = get_request_kind(req->kind);
|
|
||||||
char *host = req->url.host != NULL ? req->url.host : "";
|
|
||||||
char *path = req->url.path != NULL ? req->url.path : "";
|
|
||||||
char *query = req->url.query != NULL ? req->url.query : "";
|
|
||||||
char *fragment = req->url.fragment != NULL ? req->url.fragment : "";
|
|
||||||
|
|
||||||
snprintf(buf, len, "%s %s%s%s%s\r\n\r\n", kind, host, path, query, fragment);
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
@ -1,215 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "response.h"
|
|
||||||
|
|
||||||
#define SET_STR(segment) \
|
|
||||||
(segment) = malloc(i - start + 1); \
|
|
||||||
strncpy((segment), s + start, i - start);
|
|
||||||
|
|
||||||
#define MOVE(amount) \
|
|
||||||
i += (amount); \
|
|
||||||
start = i;
|
|
||||||
|
|
||||||
#define CHECK_AT_END() \
|
|
||||||
if (cur == '\0') { \
|
|
||||||
state = END; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define AT_DELIM (cur == '\t' && next == '\r' && next_next == '\n')
|
|
||||||
|
|
||||||
enum state {
|
|
||||||
STATUS,
|
|
||||||
|
|
||||||
MESSAGE,
|
|
||||||
MESSAGE_S,
|
|
||||||
|
|
||||||
TYPE_S,
|
|
||||||
TYPE,
|
|
||||||
|
|
||||||
LENGTH_S,
|
|
||||||
LENGTH,
|
|
||||||
|
|
||||||
HASH,
|
|
||||||
HASH_S,
|
|
||||||
|
|
||||||
CONTENT,
|
|
||||||
END
|
|
||||||
};
|
|
||||||
|
|
||||||
int parse_response(struct response *res, char *s) {
|
|
||||||
int i = 0;
|
|
||||||
int start = 0;
|
|
||||||
char cur;
|
|
||||||
char next;
|
|
||||||
char next_next;
|
|
||||||
|
|
||||||
int s_len = strlen(s);
|
|
||||||
|
|
||||||
enum state state = STATUS;
|
|
||||||
|
|
||||||
while (state != END) {
|
|
||||||
cur = s[i];
|
|
||||||
if (i < s_len)
|
|
||||||
next = s[i + 1];
|
|
||||||
if (i + 1 < s_len)
|
|
||||||
next_next = s[i + 2];
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case STATUS:
|
|
||||||
if (cur == '\r' && next == '\n') {
|
|
||||||
char *status_str;
|
|
||||||
SET_STR(status_str);
|
|
||||||
int status = atoi(status_str);
|
|
||||||
if (status > 0)
|
|
||||||
res->status = status;
|
|
||||||
else
|
|
||||||
return INVALID_STATUS;
|
|
||||||
free(status_str);
|
|
||||||
|
|
||||||
MOVE(2);
|
|
||||||
state = MESSAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case MESSAGE:
|
|
||||||
if (cur == ':') {
|
|
||||||
char *message_str;
|
|
||||||
SET_STR(message_str);
|
|
||||||
if (strncmp(message_str, "message", 7) != 0) {
|
|
||||||
MOVE(strlen(message_str) * -1);
|
|
||||||
state = TYPE;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
MOVE(1); // skip ':'
|
|
||||||
state = MESSAGE_S;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(message_str);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MESSAGE_S:
|
|
||||||
if (AT_DELIM) {
|
|
||||||
char *message_str;
|
|
||||||
SET_STR(message_str);
|
|
||||||
res->message = message_str;
|
|
||||||
|
|
||||||
MOVE(3);
|
|
||||||
state = TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case TYPE:
|
|
||||||
if (cur == ':') {
|
|
||||||
char *type_str;
|
|
||||||
SET_STR(type_str);
|
|
||||||
if (strncmp(type_str, "type", 4) != 0) {
|
|
||||||
MOVE(strlen(type_str) * -1);
|
|
||||||
state = LENGTH;
|
|
||||||
} else {
|
|
||||||
MOVE(1); // skip ':'
|
|
||||||
state = TYPE_S;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(type_str);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TYPE_S:
|
|
||||||
if (cur == '/') {
|
|
||||||
SET_STR(res->type.type);
|
|
||||||
MOVE(1);
|
|
||||||
} else if (AT_DELIM) {
|
|
||||||
SET_STR(res->type.subtype);
|
|
||||||
|
|
||||||
MOVE(3);
|
|
||||||
state = LENGTH;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LENGTH:
|
|
||||||
if (cur == ':') {
|
|
||||||
char *length_str;
|
|
||||||
SET_STR(length_str);
|
|
||||||
if (strncmp(length_str, "length", 6) != 0) {
|
|
||||||
MOVE(strlen(length_str) * -1);
|
|
||||||
state = HASH;
|
|
||||||
} else {
|
|
||||||
MOVE(1); // skip ':'
|
|
||||||
state = LENGTH_S;
|
|
||||||
}
|
|
||||||
free(length_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
case LENGTH_S:
|
|
||||||
if (AT_DELIM) {
|
|
||||||
char *length_str;
|
|
||||||
SET_STR(length_str);
|
|
||||||
res->length = atoi(length_str);
|
|
||||||
free(length_str);
|
|
||||||
|
|
||||||
MOVE(3);
|
|
||||||
state = HASH;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HASH:
|
|
||||||
if (cur == ':') {
|
|
||||||
char *hash_str;
|
|
||||||
SET_STR(hash_str);
|
|
||||||
if (strncmp(hash_str, "hash", 4) != 0) {
|
|
||||||
MOVE(strlen(hash_str) * -1);
|
|
||||||
free(hash_str);
|
|
||||||
return UNKNOWN_KEY;
|
|
||||||
} else {
|
|
||||||
MOVE(1); // skip ':'
|
|
||||||
state = HASH_S;
|
|
||||||
}
|
|
||||||
free(hash_str);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case HASH_S:
|
|
||||||
if ((cur == '\r' && next == '\n') || AT_DELIM) {
|
|
||||||
char *hash_str;
|
|
||||||
SET_STR(hash_str);
|
|
||||||
res->hash = hash_str;
|
|
||||||
|
|
||||||
MOVE(4);
|
|
||||||
state = CONTENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
case CONTENT:
|
|
||||||
if (res->length < 1) {
|
|
||||||
state = END;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == s_len - 1) {
|
|
||||||
char *content_str;
|
|
||||||
SET_STR(content_str);
|
|
||||||
res->content = content_str;
|
|
||||||
|
|
||||||
state = END;
|
|
||||||
}
|
|
||||||
|
|
||||||
case END:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
if (i == s_len) {
|
|
||||||
state = END;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void free_response(struct response *res) {
|
|
||||||
free(res->message);
|
|
||||||
|
|
||||||
free(res->type.type);
|
|
||||||
free(res->type.subtype);
|
|
||||||
|
|
||||||
free(res->hash);
|
|
||||||
free(res->content);
|
|
||||||
}
|
|
@ -1,181 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "url.h"
|
|
||||||
|
|
||||||
#define SET_STR(segment) \
|
|
||||||
(segment) = malloc(i - start + 1); \
|
|
||||||
strncpy((segment), s + start, i - start);
|
|
||||||
|
|
||||||
#define MOVE(amount) \
|
|
||||||
i += (amount); \
|
|
||||||
start = i;
|
|
||||||
|
|
||||||
#define SET_AT_END(segment) \
|
|
||||||
if (cur == '\0') { \
|
|
||||||
state = END; \
|
|
||||||
if (i - 1 > start) { \
|
|
||||||
SET_STR(segment); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
enum state { SCHEME, HOST, PORT, PATH, QUERY, FRAGMENT, END };
|
|
||||||
|
|
||||||
int parse_url(struct url *url, char *s) {
|
|
||||||
int i = 0; // index into *s
|
|
||||||
int start = 0; // index of current mode start
|
|
||||||
enum state state = SCHEME;
|
|
||||||
char cur;
|
|
||||||
|
|
||||||
while (i < MAX_URL_LENGTH) {
|
|
||||||
cur = s[i];
|
|
||||||
|
|
||||||
if (cur == ' ')
|
|
||||||
return INVALID_CHARACTER;
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case SCHEME:
|
|
||||||
if (cur == ':') {
|
|
||||||
state = HOST;
|
|
||||||
SET_STR(url->scheme);
|
|
||||||
|
|
||||||
MOVE(3); // skip the '://'
|
|
||||||
}
|
|
||||||
SET_AT_END(url->scheme);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HOST:
|
|
||||||
if (cur == ':') {
|
|
||||||
state = PORT;
|
|
||||||
SET_STR(url->host);
|
|
||||||
|
|
||||||
MOVE(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cur == '/') {
|
|
||||||
state = PATH;
|
|
||||||
SET_STR(url->host);
|
|
||||||
|
|
||||||
MOVE(0);
|
|
||||||
}
|
|
||||||
SET_AT_END(url->host);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PORT:
|
|
||||||
if (cur == '/') {
|
|
||||||
state = PATH;
|
|
||||||
char *port;
|
|
||||||
SET_STR(port);
|
|
||||||
|
|
||||||
url->port = atoi(port);
|
|
||||||
|
|
||||||
MOVE(0);
|
|
||||||
}
|
|
||||||
if (cur == '\0') {
|
|
||||||
state = END;
|
|
||||||
|
|
||||||
char *port;
|
|
||||||
SET_STR(port);
|
|
||||||
|
|
||||||
url->port = atoi(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PATH:
|
|
||||||
if (cur == '?') {
|
|
||||||
state = QUERY;
|
|
||||||
SET_STR(url->path);
|
|
||||||
|
|
||||||
MOVE(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cur == '#') {
|
|
||||||
state = FRAGMENT;
|
|
||||||
SET_STR(url->path);
|
|
||||||
|
|
||||||
MOVE(0);
|
|
||||||
}
|
|
||||||
SET_AT_END(url->path);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case QUERY:
|
|
||||||
if (cur == '#') {
|
|
||||||
state = FRAGMENT;
|
|
||||||
SET_STR(url->query);
|
|
||||||
|
|
||||||
MOVE(0);
|
|
||||||
}
|
|
||||||
SET_AT_END(url->query);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FRAGMENT:
|
|
||||||
if (cur == '\0') {
|
|
||||||
state = END;
|
|
||||||
SET_STR(url->fragment);
|
|
||||||
|
|
||||||
MOVE(0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case END:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
|
|
||||||
if (cur == '\0')
|
|
||||||
break;
|
|
||||||
if (state == END)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url->host == NULL)
|
|
||||||
return MISSING_HOST;
|
|
||||||
if (url->port == 0)
|
|
||||||
return MISSING_PORT;
|
|
||||||
|
|
||||||
if (url->path == NULL) {
|
|
||||||
url->path = malloc(2);
|
|
||||||
strcpy(url->path, "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct url *init_url(void) {
|
|
||||||
struct url *url = malloc(sizeof(struct url));
|
|
||||||
|
|
||||||
url->scheme = NULL;
|
|
||||||
url->host = NULL;
|
|
||||||
url->port = 2693;
|
|
||||||
|
|
||||||
url->path = NULL;
|
|
||||||
|
|
||||||
url->query = NULL;
|
|
||||||
url->fragment = NULL;
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
void free_url(struct url *url) {
|
|
||||||
free(url->scheme);
|
|
||||||
free(url->host);
|
|
||||||
free(url->path);
|
|
||||||
free(url->fragment);
|
|
||||||
free(url->query);
|
|
||||||
free(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
int len_url(struct url *url) {
|
|
||||||
int len = 0;
|
|
||||||
|
|
||||||
len += url->scheme != NULL ? strlen(url->scheme) : 0;
|
|
||||||
len += url->host != NULL ? strlen(url->host) : 0;
|
|
||||||
len += sizeof(url->port);
|
|
||||||
len += url->path != NULL ? strlen(url->path) : 0;
|
|
||||||
len += url->query != NULL ? strlen(url->query) : 0;
|
|
||||||
len += url->fragment != NULL ? strlen(url->fragment) : 0;
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
62
src/tui.rs
Normal file
62
src/tui.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use crossterm::event::{
|
||||||
|
Event, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags,
|
||||||
|
PushKeyboardEnhancementFlags,
|
||||||
|
};
|
||||||
|
use crossterm::terminal::{
|
||||||
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen,
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
};
|
||||||
|
use crossterm::{event, execute};
|
||||||
|
use ratatui::prelude::{CrosstermBackend, Terminal};
|
||||||
|
use std::io;
|
||||||
|
use std::io::{stdout, Stdout};
|
||||||
|
use std::panic;
|
||||||
|
|
||||||
|
pub type Tui = Terminal<CrosstermBackend<Stdout>>;
|
||||||
|
|
||||||
|
pub fn init() -> io::Result<Tui> {
|
||||||
|
execute!(stdout(), EnterAlternateScreen)?;
|
||||||
|
execute!(
|
||||||
|
stdout(),
|
||||||
|
PushKeyboardEnhancementFlags(
|
||||||
|
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES,
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
enable_raw_mode()?;
|
||||||
|
|
||||||
|
Terminal::new(CrosstermBackend::new(stdout()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore() -> io::Result<()> {
|
||||||
|
execute!(stdout(), LeaveAlternateScreen)?;
|
||||||
|
execute!(stdout(), PopKeyboardEnhancementFlags)?;
|
||||||
|
disable_raw_mode()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_event(tick: std::time::Duration) -> io::Result<Option<Event>> {
|
||||||
|
if event::poll(tick)? {
|
||||||
|
return Ok(Some(event::read()?));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install_hooks() -> eyre::Result<()> {
|
||||||
|
let hook_builder = color_eyre::config::HookBuilder::default();
|
||||||
|
let (panic_hook, eyre_hook) = hook_builder.into_hooks();
|
||||||
|
|
||||||
|
let panic_hook = panic_hook.into_panic_hook();
|
||||||
|
panic::set_hook(Box::new(move |panic_info| {
|
||||||
|
restore().unwrap();
|
||||||
|
panic_hook(panic_info);
|
||||||
|
}));
|
||||||
|
|
||||||
|
let eyre_hook = eyre_hook.into_eyre_hook();
|
||||||
|
eyre::set_hook(Box::new(move |error| {
|
||||||
|
restore().unwrap();
|
||||||
|
eyre_hook(error)
|
||||||
|
}))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
#include <ncurses.h>
|
|
||||||
|
|
||||||
#include "color.h"
|
|
||||||
|
|
||||||
void set_colors(void) {
|
|
||||||
/* fg color, bg color */
|
|
||||||
init_pair(STATUS_MAIN, COLOR_DIM_WHITE, COLOR_GREY);
|
|
||||||
init_pair(STATUS_ERROR, COLOR_DIM_WHITE, COLOR_DIM_RED);
|
|
||||||
init_pair(STATUS_PROMPT, COLOR_GREY, COLOR_DIM_WHITE);
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
#include <ncurses.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "color.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "connect.h"
|
|
||||||
#include "status.h"
|
|
||||||
#include "url.h"
|
|
||||||
|
|
||||||
void init_status(struct config *conf) {
|
|
||||||
WINDOW *status = newwin(1, conf->i.width, conf->i.height - 1, 0);
|
|
||||||
|
|
||||||
conf->i.status_win = status;
|
|
||||||
update_status(conf, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
void update_status(struct config *conf, char *s) {
|
|
||||||
werase(conf->i.status_win);
|
|
||||||
wbkgd(conf->i.status_win, COLOR_PAIR(STATUS_MAIN));
|
|
||||||
wprintw(conf->i.status_win, "%s", s);
|
|
||||||
wrefresh(conf->i.status_win);
|
|
||||||
}
|
|
||||||
|
|
||||||
void prompt_status_url(struct config *conf) {
|
|
||||||
echo();
|
|
||||||
curs_set(1);
|
|
||||||
|
|
||||||
werase(conf->i.status_win);
|
|
||||||
|
|
||||||
char prompt[] = "Enter a molerat URL: molerat://";
|
|
||||||
werase(conf->i.status_win);
|
|
||||||
wbkgd(conf->i.status_win, COLOR_PAIR(STATUS_PROMPT));
|
|
||||||
wprintw(conf->i.status_win, "%s", prompt);
|
|
||||||
wrefresh(conf->i.status_win);
|
|
||||||
|
|
||||||
char url_string[MAX_URL_LENGTH];
|
|
||||||
wgetstr(conf->i.status_win, url_string);
|
|
||||||
|
|
||||||
char url_string_with_scheme[MAX_URL_LENGTH] = "molerat://";
|
|
||||||
strlcat(url_string_with_scheme, url_string, MAX_URL_LENGTH);
|
|
||||||
|
|
||||||
conf->s.url_string = url_string_with_scheme;
|
|
||||||
|
|
||||||
curs_set(0);
|
|
||||||
noecho();
|
|
||||||
}
|
|
||||||
|
|
||||||
void error_status(struct config *conf, char *s) {
|
|
||||||
werase(conf->i.status_win);
|
|
||||||
wbkgd(conf->i.status_win, COLOR_PAIR(STATUS_ERROR));
|
|
||||||
wprintw(conf->i.status_win, "Error: %s [Press RETURN]", s);
|
|
||||||
wrefresh(conf->i.status_win);
|
|
||||||
getch();
|
|
||||||
}
|
|
11
src/util.c
11
src/util.c
@ -1,11 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdnoreturn.h>
|
|
||||||
#include <ncurses.h>
|
|
||||||
|
|
||||||
noreturn void die(const char *s) {
|
|
||||||
endwin();
|
|
||||||
|
|
||||||
perror(s);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user