aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShav Kinderlehrer <[email protected]>2024-07-23 17:48:28 -0400
committerShav Kinderlehrer <[email protected]>2024-07-23 17:48:28 -0400
commitdc0f2ce9ba97ebb47e05b80a511da6eb29818b63 (patch)
treedc83035069f5a015047be1ca3da6f65781eb4695
parentf638f4bd1e3a03bc2bdd5f9dcd57d4830fd3c553 (diff)
downloadmolehole-dc0f2ce9ba97ebb47e05b80a511da6eb29818b63.tar.gz
molehole-dc0f2ce9ba97ebb47e05b80a511da6eb29818b63.zip
Merge old-moleholencurses
-rw-r--r--Cargo.lock794
-rw-r--r--Cargo.toml13
-rwxr-xr-xMakefile32
-rwxr-xr-x[-rw-r--r--]README15
-rw-r--r--compile_flags.txt10
-rw-r--r--flake.lock6
-rw-r--r--flake.nix37
-rwxr-xr-xinclude/color.h18
-rwxr-xr-xinclude/config.h56
-rwxr-xr-xinclude/connect.h44
-rwxr-xr-xinclude/molerat.h9
-rwxr-xr-xinclude/net.h24
-rwxr-xr-xinclude/request.h22
-rwxr-xr-xinclude/response.h50
-rwxr-xr-xinclude/status.h27
-rwxr-xr-xinclude/url.h42
-rwxr-xr-xinclude/util.h11
-rw-r--r--rustfmt.toml1
-rw-r--r--src/app.rs154
-rw-r--r--src/app_action.rs24
-rw-r--r--src/app_event.rs10
-rw-r--r--src/component.rs43
-rw-r--r--src/components/global_keys.rs131
-rw-r--r--src/components/mod.rs3
-rw-r--r--src/components/status.rs86
-rw-r--r--src/components/url_manager.rs34
-rwxr-xr-xsrc/config.c18
-rw-r--r--src/keys/key_commands.rs55
-rw-r--r--src/keys/mod.rs1
-rwxr-xr-xsrc/main.c83
-rw-r--r--src/main.rs59
-rwxr-xr-xsrc/molerat/connect.c133
-rwxr-xr-xsrc/molerat/net.c68
-rwxr-xr-xsrc/molerat/request.c34
-rwxr-xr-xsrc/molerat/response.c215
-rwxr-xr-xsrc/molerat/url.c181
-rw-r--r--src/tui.rs62
-rwxr-xr-xsrc/ui/color.c10
-rwxr-xr-xsrc/ui/status.c54
-rwxr-xr-xsrc/util.c11
40 files changed, 1195 insertions, 1485 deletions
diff --git a/Cargo.lock b/Cargo.lock
deleted file mode 100644
index cb6cf1d..0000000
--- a/Cargo.lock
+++ /dev/null
@@ -1,794 +0,0 @@
-# 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",
-]
diff --git a/Cargo.toml b/Cargo.toml
deleted file mode 100644
index be10fce..0000000
--- a/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[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"
diff --git a/Makefile b/Makefile
new file mode 100755
index 0000000..7cb1bcf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,32 @@
+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)
diff --git a/README b/README
index edce122..993eaa7 100644..100755
--- a/README
+++ b/README
@@ -1,10 +1,13 @@
+===
Molehole
-========
+===
-Molehole is a (WIP) client for the Molerat protocol [0].
-It is implemented in rust using ratatui [1].
+Molehole is a WIP client for the Molerat[0] protocol.
+It does not currently function, but it is being actively developed.
-Contributions are welcome, please send them to [email protected]
+TODO:
+- Implement mtxt parser
+- Implement display
+- Implement response code handler
-[0]: https://trkt.in/molerat
-[1]: https://ratatui.rs
+[0]: http://trkt.in/molerat
diff --git a/compile_flags.txt b/compile_flags.txt
new file mode 100644
index 0000000..b6b6b96
--- /dev/null
+++ b/compile_flags.txt
@@ -0,0 +1,10 @@
+-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
diff --git a/flake.lock b/flake.lock
index 9b4400b..9dd6bad 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1709479366,
- "narHash": "sha256-n6F0n8UV6lnTZbYPl1A9q1BS0p4hduAv1mGAP17CVd0=",
+ "lastModified": 1709237383,
+ "narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "b8697e57f10292a6165a20f03d2f42920dfaf973",
+ "rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 9eceed3..b15f091 100644
--- a/flake.nix
+++ b/flake.nix
@@ -10,17 +10,42 @@
system = "aarch64-darwin";
pkgs = nixpkgs.legacyPackages.${system};
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 =
pkgs.mkShell {
buildInputs = with pkgs; [
- rustc
- rustfmt
- cargo
- rust-analyzer
- libiconv
- clippy
+ llvmPackages.clang
+ neovim
+
+ openssl.dev
+ ncurses.dev
];
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
'';
};
diff --git a/include/color.h b/include/color.h
new file mode 100755
index 0000000..0fbad4f
--- /dev/null
+++ b/include/color.h
@@ -0,0 +1,18 @@
+#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
diff --git a/include/config.h b/include/config.h
new file mode 100755
index 0000000..b90c5bd
--- /dev/null
+++ b/include/config.h
@@ -0,0 +1,56 @@
+#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
diff --git a/include/connect.h b/include/connect.h
new file mode 100755
index 0000000..d733c5e
--- /dev/null
+++ b/include/connect.h
@@ -0,0 +1,44 @@
+#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
diff --git a/include/molerat.h b/include/molerat.h
new file mode 100755
index 0000000..4c07866
--- /dev/null
+++ b/include/molerat.h
@@ -0,0 +1,9 @@
+#ifndef _MOLERAT_H_
+#define _MOLERAT_H_
+
+struct key {
+ char* key;
+ char* value;
+};
+
+#endif
diff --git a/include/net.h b/include/net.h
new file mode 100755
index 0000000..af6bf57
--- /dev/null
+++ b/include/net.h
@@ -0,0 +1,24 @@
+#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
diff --git a/include/request.h b/include/request.h
new file mode 100755
index 0000000..087b1c0
--- /dev/null
+++ b/include/request.h
@@ -0,0 +1,22 @@
+#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
diff --git a/include/response.h b/include/response.h
new file mode 100755
index 0000000..e4242a5
--- /dev/null
+++ b/include/response.h
@@ -0,0 +1,50 @@
+#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
diff --git a/include/status.h b/include/status.h
new file mode 100755
index 0000000..45f6465
--- /dev/null
+++ b/include/status.h
@@ -0,0 +1,27 @@
+#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
diff --git a/include/url.h b/include/url.h
new file mode 100755
index 0000000..c1278ee
--- /dev/null
+++ b/include/url.h
@@ -0,0 +1,42 @@
+#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
diff --git a/include/util.h b/include/util.h
new file mode 100755
index 0000000..9418824
--- /dev/null
+++ b/include/util.h
@@ -0,0 +1,11 @@
+#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
diff --git a/rustfmt.toml b/rustfmt.toml
deleted file mode 100644
index df99c69..0000000
--- a/rustfmt.toml
+++ /dev/null
@@ -1 +0,0 @@
-max_width = 80
diff --git a/src/app.rs b/src/app.rs
deleted file mode 100644
index 7d398f0..0000000
--- a/src/app.rs
+++ /dev/null
@@ -1,154 +0,0 @@
-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(())
- }
-}
diff --git a/src/app_action.rs b/src/app_action.rs
deleted file mode 100644
index 7825724..0000000
--- a/src/app_action.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-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)
- }
-}
diff --git a/src/app_event.rs b/src/app_event.rs
deleted file mode 100644
index 94a8d0d..0000000
--- a/src/app_event.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-use crossterm::event::{KeyEvent, MouseEvent};
-use url::Url;
-
-#[derive(Clone)]
-pub enum AppEvent {
- Key(KeyEvent),
- Mouse(MouseEvent),
-
- OpenUrl(Url),
-}
diff --git a/src/component.rs b/src/component.rs
deleted file mode 100644
index 3e8fd8d..0000000
--- a/src/component.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-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<()>;
-}
diff --git a/src/components/global_keys.rs b/src/components/global_keys.rs
deleted file mode 100644
index dd903b1..0000000
--- a/src/components/global_keys.rs
+++ /dev/null
@@ -1,131 +0,0 @@
-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(())
- }
-}
diff --git a/src/components/mod.rs b/src/components/mod.rs
deleted file mode 100644
index 07d34ab..0000000
--- a/src/components/mod.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub mod global_keys;
-pub mod status;
-pub mod url_manager;
diff --git a/src/components/status.rs b/src/components/status.rs
deleted file mode 100644
index aa2b384..0000000
--- a/src/components/status.rs
+++ /dev/null
@@ -1,86 +0,0 @@
-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(())
- }
-}
diff --git a/src/components/url_manager.rs b/src/components/url_manager.rs
deleted file mode 100644
index 6067923..0000000
--- a/src/components/url_manager.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-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(())
- }
-}
diff --git a/src/config.c b/src/config.c
new file mode 100755
index 0000000..19a6bfa
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,18 @@
+#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);
+}
diff --git a/src/keys/key_commands.rs b/src/keys/key_commands.rs
deleted file mode 100644
index fc06286..0000000
--- a/src/keys/key_commands.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-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
-}
diff --git a/src/keys/mod.rs b/src/keys/mod.rs
deleted file mode 100644
index c884843..0000000
--- a/src/keys/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod key_commands;
diff --git a/src/main.c b/src/main.c
new file mode 100755
index 0000000..26090ff
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,83 @@
+#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;
+}
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index 3b8bb70..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-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()
-}
diff --git a/src/molerat/connect.c b/src/molerat/connect.c
new file mode 100755
index 0000000..96c904b
--- /dev/null
+++ b/src/molerat/connect.c
@@ -0,0 +1,133 @@
+#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;
+}
diff --git a/src/molerat/net.c b/src/molerat/net.c
new file mode 100755
index 0000000..f3eef06
--- /dev/null
+++ b/src/molerat/net.c
@@ -0,0 +1,68 @@
+#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;
+}
diff --git a/src/molerat/request.c b/src/molerat/request.c
new file mode 100755
index 0000000..1d59a09
--- /dev/null
+++ b/src/molerat/request.c
@@ -0,0 +1,34 @@
+#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;
+}
diff --git a/src/molerat/response.c b/src/molerat/response.c
new file mode 100755
index 0000000..0fffe21
--- /dev/null
+++ b/src/molerat/response.c
@@ -0,0 +1,215 @@
+#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);
+}
diff --git a/src/molerat/url.c b/src/molerat/url.c
new file mode 100755
index 0000000..0fd99ea
--- /dev/null
+++ b/src/molerat/url.c
@@ -0,0 +1,181 @@
+#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;
+}
diff --git a/src/tui.rs b/src/tui.rs
deleted file mode 100644
index 911a50d..0000000
--- a/src/tui.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-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(())
-}
diff --git a/src/ui/color.c b/src/ui/color.c
new file mode 100755
index 0000000..e834193
--- /dev/null
+++ b/src/ui/color.c
@@ -0,0 +1,10 @@
+#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);
+}
diff --git a/src/ui/status.c b/src/ui/status.c
new file mode 100755
index 0000000..703575d
--- /dev/null
+++ b/src/ui/status.c
@@ -0,0 +1,54 @@
+#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();
+}
diff --git a/src/util.c b/src/util.c
new file mode 100755
index 0000000..2015a08
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,11 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <ncurses.h>
+
+noreturn void die(const char *s) {
+ endwin();
+
+ perror(s);
+ exit(EXIT_FAILURE);
+}