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 old mode 100644 new mode 100755 index edce122..993eaa7 --- 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 molerat@git.trinket.icu +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 + +/** + * 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 +#include + +#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 + +#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 + +/** + * 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>, - pub key_commands: Vec, - - should_quit: bool, -} - -impl App { - pub fn new(tick_rate: Duration) -> Result { - 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 = 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 = 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 = 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 { - 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 { - None - } - - #[allow(unused)] - fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Option { - None - } - - #[allow(unused)] - fn update(&mut self) -> Option { - 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, - - 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 { - 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 = 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, -} - -impl Component for StatusBar { - fn handle_key_event( - &mut self, - key: crossterm::event::KeyEvent, - ) -> Option { - 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 { - 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, -} - -impl Component for UrlManager { - fn handle_event(&mut self, event: AppEvent) -> Option { - 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 +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +#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 +#include +#include + +#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 +#include + +#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 +#include + +#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>; - -pub fn init() -> io::Result { - 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> { - 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 + +#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 +#include + +#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 +#include +#include +#include + +noreturn void die(const char *s) { + endwin(); + + perror(s); + exit(EXIT_FAILURE); +}