Compare commits

..

20 Commits

Author SHA1 Message Date
Thomas Eppers
7e1ee0f699 strip release build of debuginfo
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-03-04 23:52:18 +01:00
Thomas Eppers
dee25f911b changed output formatting
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-02-20 14:31:07 +01:00
Thomas Eppers
445b21975a simplified logic a bit 2023-02-20 14:14:16 +01:00
Thomas Eppers
70b163cf81 do not block when fetching new tags 2023-02-20 13:56:33 +01:00
Thomas Eppers
431b062420 simplified logic 2023-02-19 17:06:55 +01:00
Thomas Eppers
bd829f23ad better error handling 2023-02-19 12:39:15 +01:00
Thomas Eppers
28ae313797 more idiomatic code 2023-02-19 11:48:16 +01:00
Thomas Eppers
1c72d4cd24 refreshing the ui pnly on input or when new data is available 2023-02-19 11:40:40 +01:00
Thomas Eppers
017bafa19b fixed not refreshing on moving cursor on tags up 2023-02-19 11:40:11 +01:00
Thomas Eppers
646e48a208 created specific UiEvents; moved code from asnyc_tag_list to ui files 2023-02-17 19:26:24 +01:00
Thomas Eppers
1f2a098c26 applied fixes to no_yaml_found 2023-02-17 17:23:14 +01:00
Thomas Eppers
f4132d4125 simplified logic 2023-02-17 17:03:14 +01:00
Thomas Eppers
7fb519e70b added a message when fetching next page of tags 2023-02-17 17:00:20 +01:00
Thomas Eppers
70cf17f1c7 changed handling input a bit; j and k can select now; show more info when
fetching tags
2023-02-17 16:23:51 +01:00
Thomas Eppers
935a9e5709 changed infor widget a little to make it smaller 2023-02-17 16:23:21 +01:00
Thomas Eppers
a517c7d6d0 added few documentation strings 2023-02-17 16:23:01 +01:00
Thomas Eppers
308fa8dbb8 fixed tests 2023-02-17 13:23:08 +01:00
Thomas Eppers
cd0479a618 using rustls instead of openssl 2023-02-17 12:40:45 +01:00
Thomas Eppers
87039541a5 rewrote parts to make it more async 2023-01-31 23:51:45 +01:00
Thomas Eppers
c98558c41d merged Error into 1; simplified some logic 2023-01-29 19:36:58 +01:00
17 changed files with 992 additions and 839 deletions

496
Cargo.lock generated
View File

@ -32,16 +32,16 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
@ -113,22 +113,6 @@ dependencies = [
"vec_map",
]
[[package]]
name = "core-foundation"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "encoding_rs"
version = "0.8.28"
@ -144,21 +128,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
@ -171,62 +140,54 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.16"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.16"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
[[package]]
name = "futures-io"
version = "0.3.16"
name = "futures-macro"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582"
[[package]]
name = "futures-sink"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53"
[[package]]
name = "futures-task"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2"
[[package]]
name = "futures-util"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
"autocfg",
"futures-core",
"futures-io",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "getrandom"
version = "0.2.3"
name = "futures-sink"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
[[package]]
name = "futures-task"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
[[package]]
name = "futures-util"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
dependencies = [
"cfg-if",
"libc",
"wasi",
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
@ -272,6 +233,15 @@ dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "http"
version = "0.2.4"
@ -331,16 +301,18 @@ dependencies = [
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
name = "hyper-rustls"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
dependencies = [
"bytes",
"futures-util",
"hyper",
"native-tls",
"log",
"rustls",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"webpki",
]
[[package]]
@ -393,9 +365,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.99"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "log"
@ -426,51 +398,14 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mio"
version = "0.7.13"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]]
name = "native-tls"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
]
[[package]]
@ -494,11 +429,11 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.13.0"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"hermit-abi 0.2.6",
"libc",
]
@ -514,39 +449,6 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "openssl"
version = "0.10.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-sys",
]
[[package]]
name = "openssl-probe"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
[[package]]
name = "openssl-sys"
version = "0.9.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -565,18 +467,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@ -619,46 +509,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
@ -691,6 +541,7 @@ dependencies = [
"structopt",
"termion",
"thiserror",
"tokio",
"tui",
]
@ -711,15 +562,6 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "reqwest"
version = "0.11.4"
@ -734,27 +576,56 @@ dependencies = [
"http",
"http-body",
"hyper",
"hyper-tls",
"hyper-rustls",
"ipnet",
"js-sys",
"lazy_static",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rustls"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
dependencies = [
"base64",
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "ryu"
version = "1.0.5"
@ -762,36 +633,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "schannel"
version = "0.1.19"
name = "sct"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [
"lazy_static",
"winapi",
]
[[package]]
name = "security-framework"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284"
dependencies = [
"core-foundation-sys",
"libc",
"ring",
"untrusted",
]
[[package]]
@ -845,14 +693,20 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
[[package]]
name = "socket2"
version = "0.4.1"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "strsim"
version = "0.8.0"
@ -894,20 +748,6 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "tempfile"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if",
"libc",
"rand",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "termion"
version = "1.5.6"
@ -956,7 +796,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
@ -977,9 +817,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.10.0"
version = "1.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b"
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
dependencies = [
"autocfg",
"bytes",
@ -988,17 +828,31 @@ dependencies = [
"mio",
"num_cpus",
"pin-project-lite",
"winapi",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.0"
name = "tokio-macros"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"native-tls",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-rustls"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
dependencies = [
"rustls",
"tokio",
"webpki",
]
[[package]]
@ -1093,6 +947,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.2.2"
@ -1105,12 +965,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
@ -1139,6 +993,12 @@ version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.75"
@ -1217,6 +1077,25 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
dependencies = [
"webpki",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -1239,6 +1118,63 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
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.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winreg"
version = "0.7.0"

View File

@ -9,7 +9,7 @@ edition = "2021"
[dependencies]
serde = { version = "1.0.127", features = ["derive"] }
serde_json = "1.0.66"
reqwest = { version = "0.11.4", features = ["blocking", "json"] }
reqwest = { version = "0.11.4", default-features = false, features = ["json", "rustls-tls"] }
chrono = "0.4.19"
tui = "0.16"
termion = "1.5"
@ -18,7 +18,9 @@ lazy_static = "1.4.0"
structopt = "0.3.23"
thiserror = "1.0.32"
anyhow = "1.0.59"
tokio = { version = "1.24.2", features = ["macros", "rt-multi-thread"] }
[profile.release]
lto = "yes"
opt-level = "z"
strip="debuginfo"

32
src/error.rs Normal file
View File

@ -0,0 +1,32 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
/// no valid tag found
#[error("Expected a tag")]
NoTagFound,
/// input that can't be interpreted
#[error("Unexpected input")]
MisformedInput,
/// a serde error
#[error("Converting error: {0}")]
Converting(String),
/// invalid repos show a valid json with 0 tags
#[error("Given Repo does not exists or has 0 tags.")]
NoTagsFound,
/// converting serde error
#[error("Serde error: {0}")]
Serde(#[from] serde_json::Error),
/// error while handling requests
#[error("reqwest error: {0}")]
Reqwest(#[from] reqwest::Error),
/// error while sending to channel
#[error("sending to channel error: {0}")]
ChannelSendError(#[from] std::sync::mpsc::SendError<crate::ui::UiEvent>),
}

View File

@ -1,4 +1,5 @@
mod common;
mod error;
mod repo;
mod repository;
mod ui;

View File

@ -1,21 +1,6 @@
use std::fmt;
use regex::Regex;
#[derive(Debug, PartialEq)]
pub enum Error {
NoTagFound,
MisformedInput,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::NoTagFound => write!(f, "Expected a tag"),
Error::MisformedInput => write!(f, "Unexpected input"),
}
}
}
use crate::error::Error;
#[derive(Debug, PartialEq)]
pub enum Repo {
@ -102,80 +87,89 @@ mod tests {
use crate::repo::{Error, Repo};
#[test]
fn test_split_repo_without_tag() {
let input: Vec<(&str, Result<Repo, Error>)> = vec![
("", Err(Error::MisformedInput)),
("NGINX", Err(Error::MisformedInput)),
("nginx", Ok(Repo::Project("nginx".into()))),
fn test_split_repo_without_tag_error() {
let input: Vec<&str> = vec!["", "NGINX"];
for i in input {
assert!(super::split_repo_without_tag(i).is_err());
}
}
#[test]
fn test_split_repo_without_tag() -> Result<(), Error> {
let input: Vec<(&str, Repo)> = vec![
("nginx", Repo::Project("nginx".into())),
(
"library/nginx",
Ok(Repo::WithOrga("library".into(), "nginx".into())),
Repo::WithOrga("library".into(), "nginx".into()),
),
(
"ghcr.io/library/nginx",
Ok(Repo::WithServer(
"ghcr.io".into(),
"library".into(),
"nginx".into(),
)),
Repo::WithServer("ghcr.io".into(), "library".into(), "nginx".into()),
),
(
"te-st/test-hypen",
Ok(Repo::WithOrga("te-st".into(), "test-hypen".into())),
Repo::WithOrga("te-st".into(), "test-hypen".into()),
),
(
"test/test.dot",
Ok(Repo::WithOrga("test".into(), "test.dot".into())),
Repo::WithOrga("test".into(), "test.dot".into()),
),
];
for i in input {
assert_eq!(super::split_repo_without_tag(i.0), i.1);
assert_eq!(super::split_repo_without_tag(i.0)?, i.1);
}
Ok(())
}
#[test]
fn test_match_yaml_image_error() {
let input: Vec<&str> = vec!["", "version: '2'", "image: ", " image: "];
for i in input {
assert!(super::match_yaml_image(i).is_err());
}
}
#[test]
fn test_match_yaml_image() {
let input: Vec<(&str, Result<(&str, &str), Error>)> = vec![
("", Err(Error::NoTagFound)),
("version: '2'", Err(Error::NoTagFound)),
("image: ", Err(Error::NoTagFound)),
(" image: ", Err(Error::NoTagFound)),
(" image: nginx", Ok((" image: ", "nginx"))),
(" image: library/nginx", Ok((" image: ", "library/nginx"))),
fn test_match_yaml_image() -> Result<(), Error> {
let input: Vec<(&str, (&str, &str))> = vec![
(" image: nginx", (" image: ", "nginx")),
(" image: library/nginx", (" image: ", "library/nginx")),
(
" image: gchr.io/library/nginx",
Ok((" image: ", "gchr.io/library/nginx")),
" image: ghcr.io/library/nginx",
(" image: ", "ghcr.io/library/nginx"),
),
(" image: nginx # comment", Ok((" image: ", "nginx"))),
(" image: test-hyphen", Ok((" image: ", "test-hyphen"))),
(" image: test.dot", Ok((" image: ", "test.dot"))),
(" image: nginx # comment", (" image: ", "nginx")),
(" image: test-hyphen", (" image: ", "test-hyphen")),
(" image: test.dot", (" image: ", "test.dot")),
];
for i in input {
assert_eq!(super::match_yaml_image(i.0), i.1);
assert_eq!(super::match_yaml_image(i.0)?, i.1);
}
Ok(())
}
#[test]
fn test_split_tag_from_repo() {
let input: Vec<(&str, Result<(&str, &str), super::Error>)> = vec![
("nginx", Ok(("nginx", ""))),
("library/nginx", Ok(("library/nginx", ""))),
("ghcr.io/library/nginx", Ok(("ghcr.io/library/nginx", ""))),
("nginx:", Ok(("nginx", ""))),
("nginx:1", Ok(("nginx", "1"))),
("nginx:latest", Ok(("nginx", "latest"))),
("hy-phen:latest", Ok(("hy-phen", "latest"))),
("test.dot:latest", Ok(("test.dot", "latest"))),
fn test_split_tag_from_repo() -> Result<(), Error> {
let input: Vec<(&str, (&str, &str))> = vec![
("nginx", ("nginx", "")),
("library/nginx", ("library/nginx", "")),
("ghcr.io/library/nginx", ("ghcr.io/library/nginx", "")),
("nginx:", ("nginx", "")),
("nginx:1", ("nginx", "1")),
("nginx:latest", ("nginx", "latest")),
("hy-phen:latest", ("hy-phen", "latest")),
("test.dot:latest", ("test.dot", "latest")),
(
"woodpeckerci/woodpecker-server",
Ok(("woodpeckerci/woodpecker-server", "")),
("woodpeckerci/woodpecker-server", ""),
),
];
for i in input {
assert_eq!(super::split_tag_from_repo(i.0), i.1);
assert_eq!(super::split_tag_from_repo(i.0)?, i.1);
}
Ok(())
}
}

View File

@ -1,6 +1,6 @@
use serde::Deserialize;
use crate::repository::Error;
use crate::error::Error;
#[derive(Deserialize, Debug, Clone)]
struct ImageDetails {
@ -46,24 +46,17 @@ pub struct DockerHub {
impl DockerHub {
/// fetches tag information with a repository name in the form of organization/repository or library/repository in the case of official images from docker
pub fn create_repo(repo: &str) -> Result<super::Repo, Error> {
pub async fn create_repo(repo: &str) -> Result<super::Repo, Error> {
let request = format!("https://hub.docker.com/v2/repositories/{}/tags", repo);
Self::with_url(&request)
Self::with_url(&request).await
}
/// fetches tag information from a url
pub fn with_url(url: &str) -> Result<super::Repo, Error> {
let response = match reqwest::blocking::get(url) {
Ok(result) => result,
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
};
pub async fn with_url(url: &str) -> Result<super::Repo, Error> {
let response = reqwest::get(url).await?;
//convert it to json
let tags = match response.json::<Self>() {
Ok(result) => result,
Err(e) => return Err(Error::Converting(format!("invalid json: {}", e))),
};
let tags = response.json::<Self>().await?;
if tags.results.is_empty() {
return Err(Error::NoTagsFound);
}

View File

@ -1,24 +1,11 @@
mod dockerhub;
use chrono::DateTime;
use thiserror::Error;
use crate::common::display_duration_ext::DisplayDurationExt;
use crate::error::Error;
use crate::repo;
#[derive(Debug, PartialEq, Error)]
pub enum Error {
/// couldn't fetch json with reqwest
#[error("Fetching error: {0}")]
Fetching(String),
/// a serde error
#[error("Converting error: {0}")]
Converting(String),
/// invalid repos show a valid json with 0 tags
#[error("Given Repo does not exists or has 0 tags.")]
NoTagsFound,
}
#[derive(Clone, PartialEq)]
pub struct TagDetails {
pub arch: Option<String>,
@ -59,13 +46,14 @@ impl Tag {
}
}
#[derive(Clone)]
pub struct Repo {
tags: Vec<Tag>,
next_page: Option<String>,
}
impl Repo {
pub fn new(repo: &str) -> Result<Self, Error> {
pub async fn new(repo: &str) -> Result<Self, Error> {
use crate::repo::Repo;
let (registry, repo) = match crate::repo::split_repo_without_tag(repo) {
Ok(Repo::WithServer(reg, org, pro)) => (Some(reg), format!("{}/{}", org, pro)),
@ -75,42 +63,41 @@ impl Repo {
};
if registry.unwrap_or_default().is_empty() {
dockerhub::DockerHub::create_repo(&repo)
dockerhub::DockerHub::create_repo(&repo).await
} else {
Err(Error::Converting("This registry is not supported".into()))
Err(Error::Converting(
"This registry is not supported yet".into(),
))
}
}
pub fn with_url(url: &str) -> Result<Self, Error> {
pub async fn with_url(url: &str) -> Result<Self, Error> {
//TODO fix for other registries
dockerhub::DockerHub::with_url(url)
dockerhub::DockerHub::with_url(url).await
}
pub fn get_tags(&self) -> &Vec<Tag> {
&self.tags
}
pub fn next_page(&self) -> Option<Self> {
match &self.next_page {
Some(url) => match Self::with_url(url) {
Ok(tags) => Some(tags),
Err(_) => None,
},
None => None,
pub async fn next_page(&self) -> Option<Self> {
if let Some(url) = &self.next_page {
match Self::with_url(url).await {
Ok(tags) => return Some(tags),
Err(e) => println!("Encountered error: {e}"),
}
}
None
}
}
/// checks the repo name and may add a prefix for official images
pub fn check_repo(name: &str) -> Result<String, Error> {
let repo = match repo::split_tag_from_repo(name) {
Err(e) => return Err(Error::Converting(format!("{}", e))),
Ok((name, _)) => name,
};
let repo = repo::split_tag_from_repo(name)?;
match repo::split_repo_without_tag(name) {
Ok(repo::Repo::Project(s)) => Ok(format!("library/{}", s)),
Ok(_) => Ok(repo.to_string()),
Ok(_) => Ok(repo.0.to_string()),
Err(e) => Err(Error::Converting(format!("{}", e))),
}
}

View File

@ -1,225 +0,0 @@
use anyhow::Result;
use termion::event::Key;
use termion::raw::IntoRawMode;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::Terminal;
use std::{io, thread};
use crate::repository;
use crate::widget::info;
use crate::widget::repo_entry;
use crate::widget::service_switcher;
use crate::widget::tag_list;
use crate::Opt;
pub struct Ui {
state: State,
repo: crate::widget::repo_entry::RepoEntry,
tags: crate::widget::tag_list::TagList,
services: crate::widget::service_switcher::ServiceSwitcher,
details: crate::widget::details::Details,
info: crate::widget::info::Info,
}
#[derive(PartialEq, Clone)]
pub enum State {
EditRepo,
SelectTag,
SelectService,
}
impl std::fmt::Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
State::EditRepo => write!(f, "Edit repository"),
State::SelectTag => write!(f, "Select a tag"),
State::SelectService => write!(f, "Select a image"),
}
}
}
impl std::iter::Iterator for State {
type Item = Self;
fn next(&mut self) -> Option<Self::Item> {
match self {
State::EditRepo => *self = State::SelectTag,
State::SelectTag => *self = State::SelectService,
State::SelectService => *self = State::EditRepo,
}
Some(self.clone())
}
}
impl Ui {
pub fn run(opt: &Opt, switcher: service_switcher::ServiceSwitcher) -> Result<()> {
let repo_id = opt.repo.as_deref();
let mut ui = Ui {
state: State::SelectService,
repo: repo_entry::RepoEntry::new(repo_id),
tags: tag_list::TagList::with_status("Tags are empty"),
services: switcher,
details: crate::widget::details::Details::new(),
info: info::Info::new("Select image of edit Repository"),
};
if opt.repo.is_none() {
ui.tags = tag_list::TagList::with_repo_name(ui.repo.get());
}
//setup tui
let stdout = io::stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
//setup input thread
let receiver = super::spawn_stdin_channel();
//core interaction loop
'core: loop {
//draw
terminal.draw(|rect| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length(10),
Constraint::Length(3),
Constraint::Min(7),
Constraint::Length(2),
]
.as_ref(),
)
.split(rect.size());
let (list, state) = ui.services.render(ui.state == State::SelectService);
rect.render_stateful_widget(list, chunks[0], state);
rect.render_widget(ui.repo.render(ui.state == State::EditRepo), chunks[1]);
let (list, state) = ui.tags.render(ui.state == State::SelectTag);
let more_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Min(15), Constraint::Length(28)].as_ref())
.split(chunks[2]);
rect.render_stateful_widget(list, more_chunks[0], state);
rect.render_widget(ui.details.render(), more_chunks[1]);
rect.render_widget(ui.info.render(), chunks[3]);
})?;
//handle input
match receiver.try_recv() {
Ok(Key::Ctrl('q')) => break 'core, //quit program without saving
Ok(Key::Char('\t')) => {
ui.state.next();
ui.info.set_info(&ui.state);
}
Ok(Key::Ctrl('s')) => match ui.services.save() {
Err(e) => {
ui.info.set_info(&format!("{}", e));
continue;
}
Ok(_) => ui.info.set_text("Saved compose file"),
},
Ok(Key::Ctrl('r')) => {
ui.repo.confirm();
ui.tags = tag_list::TagList::with_repo_name(ui.repo.get());
}
Ok(Key::Char('\n')) => match ui.state {
State::EditRepo => {
ui.repo.confirm();
ui.tags = tag_list::TagList::with_repo_name(ui.repo.get());
}
State::SelectTag => {
let mut repo = ui.repo.get();
let tag = match ui.tags.get_selected() {
Err(tag_list::Error::NextPageSelected) => continue,
Err(e) => {
ui.info.set_info(&format!("{}", e));
continue;
}
Ok(tag) => tag,
};
repo.push(':');
repo.push_str(&tag);
ui.services.change_current_line(repo);
}
_ => (),
},
Ok(Key::Char(key)) => match ui.state {
State::SelectService => (),
State::EditRepo => {
ui.info.set_text("Editing Repository");
ui.repo.handle_input(Key::Char(key));
}
State::SelectTag => (),
},
Ok(Key::Backspace) => match ui.state {
State::SelectService => (),
State::EditRepo => {
ui.info.set_text("Editing Repository");
ui.repo.handle_input(Key::Backspace);
}
State::SelectTag => (),
},
Ok(Key::Up) => match ui.state {
State::SelectService if ui.services.find_previous_match() => {
match ui.services.extract_repo() {
Err(e) => ui.info.set_info(&format!("{}", e)),
Ok(s) => {
let repo = match repository::check_repo(&s) {
Err(e) => {
ui.info.set_info(&format!("{}", e));
continue;
}
Ok(s) => s,
};
ui.repo.set(repo.to_string());
ui.tags = tag_list::TagList::with_repo_name(ui.repo.get());
}
}
}
State::SelectService => (),
State::EditRepo => (),
State::SelectTag => {
ui.tags.handle_input(Key::Up);
ui.details = ui.tags.create_detail_widget();
}
},
Ok(Key::Down) => match ui.state {
State::SelectService if ui.services.find_next_match() => {
match ui.services.extract_repo() {
Err(e) => ui.info.set_info(&format!("{}", e)),
Ok(s) => {
let repo = match repository::check_repo(&s) {
Err(e) => {
ui.info.set_info(&format!("{}", e));
continue;
}
Ok(s) => s,
};
ui.repo.set(repo.to_string());
ui.tags = tag_list::TagList::with_repo_name(ui.repo.get());
}
}
}
State::SelectService => (),
State::EditRepo => (),
State::SelectTag => {
ui.tags.handle_input(Key::Down);
ui.details = ui.tags.create_detail_widget();
}
},
_ => (),
}
//sleep for 32ms (30 fps)
thread::sleep(std::time::Duration::from_millis(32));
}
terminal.clear()?;
Ok(())
}
}

View File

@ -1,35 +1,22 @@
mod default;
mod no_yaml;
mod no_yaml_found;
mod yaml_found;
use anyhow::Result;
use termion::input::TermRead;
use crate::widget::service_switcher;
use crate::Opt;
use std::sync::mpsc;
use std::{io, thread};
pub enum UiEvent {
Input(termion::event::Key),
RefreshOnNewData,
}
pub fn create_ui(opt: &Opt) -> Result<()> {
let service_result = service_switcher::ServiceSwitcher::new(&opt.file);
match service_result {
None => no_yaml::NoYaml::run(opt),
Some(switcher) => default::Ui::run(opt, switcher),
Some(switcher) => yaml_found::Ui::run(opt, switcher),
_ => no_yaml_found::Ui::run(opt),
}?;
Ok(())
}
/// create a thread for catching input and send them to core loop
pub fn spawn_stdin_channel() -> mpsc::Receiver<termion::event::Key> {
let (tx, rx) = mpsc::channel::<termion::event::Key>();
thread::spawn(move || loop {
let stdin = io::stdin();
for c in stdin.keys() {
tx.send(c.unwrap()).unwrap();
}
});
thread::sleep(std::time::Duration::from_millis(64));
rx
}

View File

@ -1,161 +0,0 @@
use anyhow::Result;
use termion::event::Key;
use termion::raw::IntoRawMode;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::Terminal;
use std::{io, thread};
use crate::widget::details;
use crate::widget::info;
use crate::widget::repo_entry;
use crate::widget::tag_list;
use crate::Opt;
#[derive(PartialEq, Clone)]
pub enum State {
EditRepo,
SelectTag,
}
impl std::fmt::Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
State::EditRepo => write!(f, "Edit repository"),
State::SelectTag => write!(f, "Select a tag"),
}
}
}
impl std::iter::Iterator for State {
type Item = Self;
fn next(&mut self) -> Option<Self::Item> {
match self {
State::EditRepo => *self = State::SelectTag,
State::SelectTag => *self = State::EditRepo,
}
Some(self.clone())
}
}
pub struct NoYaml {
state: State,
repo: repo_entry::RepoEntry,
tags: tag_list::TagList,
details: details::Details,
info: info::Info,
}
impl NoYaml {
pub fn run(opt: &Opt) -> Result<()> {
let repo_id = opt.repo.as_deref();
let mut ui = NoYaml {
state: State::EditRepo,
repo: repo_entry::RepoEntry::new(repo_id),
tags: tag_list::TagList::with_status("Tags are empty"),
details: details::Details::new(),
info: info::Info::new("could not find a docker-compose file"),
};
// load tags if a repository was given thorugh paramter
if opt.repo.is_none() {
ui.tags = tag_list::TagList::with_repo_name(ui.repo.get());
}
//setup tui
let stdout = io::stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
//setup input thread
let receiver = super::spawn_stdin_channel();
//core interaction loop
'core: loop {
//draw
terminal.draw(|rect| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length(3),
Constraint::Min(7),
Constraint::Length(2),
]
.as_ref(),
)
.split(rect.size());
rect.render_widget(ui.repo.render(ui.state == State::EditRepo), chunks[0]);
let (list, state) = ui.tags.render(ui.state == State::SelectTag);
let more_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Min(15), Constraint::Length(28)].as_ref())
.split(chunks[1]);
rect.render_stateful_widget(list, more_chunks[0], state);
rect.render_widget(ui.details.render(), more_chunks[1]);
rect.render_widget(ui.info.render(), chunks[2]);
})?;
//handle input
match receiver.try_recv() {
Ok(Key::Ctrl('q')) => break 'core,
Ok(Key::Char('\t')) => {
ui.state.next();
ui.info.set_info(&ui.state);
}
Ok(Key::Ctrl('r')) => {
ui.repo.confirm();
ui.tags = tag_list::TagList::with_repo_name(ui.repo.get());
}
Ok(Key::Char('\n')) => match ui.state {
State::EditRepo => {
ui.repo.confirm();
ui.tags = tag_list::TagList::with_repo_name(ui.repo.get());
}
State::SelectTag => ui.tags.handle_input(Key::Char('\n')),
},
Ok(Key::Char(key)) => match ui.state {
State::EditRepo => {
ui.info.set_text("Editing Repository");
ui.repo.handle_input(Key::Char(key));
}
State::SelectTag => {
ui.tags.handle_input(Key::Char(key));
}
},
Ok(Key::Backspace) => match ui.state {
State::EditRepo => {
ui.info.set_text("Editing Repository");
ui.repo.handle_input(Key::Backspace);
}
State::SelectTag => (),
},
Ok(Key::Up) => match ui.state {
State::EditRepo => (),
State::SelectTag => {
ui.tags.handle_input(Key::Up);
ui.details = ui.tags.create_detail_widget();
}
},
Ok(Key::Down) => match ui.state {
State::EditRepo => (),
State::SelectTag => {
ui.tags.handle_input(Key::Down);
ui.details = ui.tags.create_detail_widget();
}
},
_ => (),
}
//sleep for 32ms (30 fps)
thread::sleep(std::time::Duration::from_millis(32));
}
terminal.clear()?;
Ok(())
}
}

263
src/ui/no_yaml_found.rs Normal file
View File

@ -0,0 +1,263 @@
use anyhow::Result;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::Terminal;
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use super::UiEvent;
use crate::error::Error;
use crate::widget::async_tag_list::{self, TagList};
use crate::widget::{info, repo_entry};
use crate::Opt;
pub struct Ui {
state: State,
repo: repo_entry::RepoEntry,
tags: async_tag_list::TagList,
details: crate::widget::details::Details,
info: info::Info,
}
#[derive(PartialEq, Clone)]
pub enum State {
EditRepo,
SelectTag,
}
impl std::fmt::Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
State::EditRepo => write!(f, "Edit repository"),
State::SelectTag => write!(f, "Select a tag"),
}
}
}
impl std::iter::Iterator for State {
type Item = Self;
fn next(&mut self) -> Option<Self::Item> {
match self {
State::EditRepo => *self = State::SelectTag,
State::SelectTag => *self = State::EditRepo,
}
Some(self.clone())
}
}
pub enum DeferredEvent {
Quit,
NewRepo(String),
LoadMoreTags,
}
impl Ui {
/// catch input and send them to core loop
pub fn wait_for_input(sender: mpsc::Sender<UiEvent>) -> Result<(), Error> {
let stdin = std::io::stdin();
for c in stdin.keys() {
sender.send(UiEvent::Input(c.unwrap()))?;
}
Ok(())
}
#[tokio::main]
pub async fn work_requests(
ui: &Arc<Mutex<Ui>>,
events: mpsc::Receiver<DeferredEvent>,
sender: mpsc::Sender<UiEvent>,
) -> Result<(), Error> {
use std::sync::atomic::Ordering;
let fetching_tags = Arc::new(std::sync::atomic::AtomicBool::new(false));
loop {
match events.recv() {
Ok(DeferredEvent::Quit) => break,
Ok(DeferredEvent::NewRepo(name)) => {
{
let mut ui = ui.lock().unwrap();
ui.tags = TagList::with_status("fetching new tags...");
sender.send(UiEvent::RefreshOnNewData)?;
}
let list = async_tag_list::TagList::with_repo_name(name).await;
let mut ui = ui.lock().unwrap();
ui.tags = list;
}
Ok(DeferredEvent::LoadMoreTags) if !fetching_tags.load(Ordering::Relaxed) => {
fetching_tags.store(true, Ordering::Relaxed);
let mut tags_copy = {
let mut ui = ui.lock().unwrap();
ui.info.set_text("Fetching more tags...");
sender.send(UiEvent::RefreshOnNewData)?;
ui.tags.clone()
};
let sender_copy = sender.clone();
let ui_copy = ui.clone();
let fetching_tags_copy = fetching_tags.clone();
std::thread::spawn(move || {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
tags_copy.load_next_page().await;
let mut ui = ui_copy.lock().unwrap();
//set position to the position of old TagList
//it may have changed since tag fetching has been invoked
tags_copy.set_cursor(ui.tags.get_cursor().clone());
ui.tags = tags_copy;
ui.details = ui.tags.create_detail_widget();
ui.info.set_text("Fetching tags done");
sender_copy.send(UiEvent::RefreshOnNewData).unwrap();
fetching_tags_copy.store(false, Ordering::Relaxed);
})
});
}
Ok(DeferredEvent::LoadMoreTags) => {
//do nothing, as we are fetching tags
}
Err(e) => {
let mut ui = ui.lock().unwrap();
ui.info.set_info(&e);
}
};
sender.send(UiEvent::RefreshOnNewData)?;
}
Ok(())
}
pub fn run(opt: &Opt) -> Result<()> {
let repo_id = opt.repo.as_deref();
let ui = Arc::new(Mutex::new(Ui {
state: State::EditRepo,
repo: repo_entry::RepoEntry::new(repo_id),
tags: async_tag_list::TagList::with_status("no tags"),
details: crate::widget::details::Details::new(),
info: info::Info::new("Select image or edit Repository"),
}));
// spawn new thread that fetches information async
let (sender, receiver) = mpsc::channel();
let (deferred_sender, deferred_receiver) = mpsc::channel();
let ui_clone = ui.clone();
let sender2 = sender.clone();
std::thread::spawn(move || {
if let Err(e) = Self::work_requests(&ui_clone, deferred_receiver, sender2) {
let mut ui = ui_clone.lock().unwrap();
ui.info.set_info(&e);
}
});
//setup tui
let stdout = std::io::stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
//setup input thread
let ui_clone = ui.clone();
std::thread::spawn(move || {
if let Err(e) = Self::wait_for_input(sender) {
let mut ui = ui_clone.lock().unwrap();
ui.info.set_info(&e);
}
});
//core interaction loop
'core: loop {
let mut ui_data = ui.lock().unwrap();
//draw
terminal.draw(|rect| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length(3),
Constraint::Min(7),
Constraint::Length(2),
]
.as_ref(),
)
.split(rect.size());
rect.render_widget(
ui_data.repo.render(ui_data.state == State::EditRepo),
chunks[0],
);
let render_state = ui_data.state == State::SelectTag;
let (tags, state) = ui_data.tags.render(render_state);
let more_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Min(15), Constraint::Length(30)].as_ref())
.split(chunks[1]);
rect.render_stateful_widget(tags, more_chunks[0], state);
rect.render_widget(ui_data.details.render(), more_chunks[1]);
rect.render_widget(ui_data.info.render(), chunks[2]);
})?;
drop(ui_data);
//handle input
//wait before locking
let event = receiver.recv();
let mut ui_data = ui.lock().unwrap();
match event {
Ok(UiEvent::Input(key)) => match key {
//quit without saving
Key::Ctrl('q') | Key::Ctrl('c') => {
deferred_sender.send(DeferredEvent::Quit)?;
break 'core; //quit program without saving
}
//cycle widgets
Key::Char('\t') => {
ui_data.state.next();
let state = ui_data.state.clone();
ui_data.info.set_info(&state);
}
//refresh repository
Key::Ctrl('r') => {
ui_data.repo.confirm();
deferred_sender.send(DeferredEvent::NewRepo(ui_data.repo.get()))?;
}
//enter on selecting tags
Key::Char('\n') if ui_data.state == State::EditRepo => {
ui_data.repo.confirm();
deferred_sender.send(DeferredEvent::NewRepo(ui_data.repo.get()))?;
}
//delete last char on repository
Key::Backspace if ui_data.state == State::EditRepo => {
ui_data.info.set_text("Editing Repository");
ui_data.repo.handle_input(Key::Backspace);
}
//moving up on selecting tags
Key::Up | Key::Char('k') if ui_data.state == State::SelectTag => {
ui_data.tags.previous();
ui_data.details = ui_data.tags.create_detail_widget();
}
//moving down on selecting tags
Key::Down | Key::Char('j') if ui_data.state == State::SelectTag => {
if ui_data.tags.next().is_some() {
deferred_sender.send(DeferredEvent::LoadMoreTags).unwrap();
}
ui_data.details = ui_data.tags.create_detail_widget();
}
//append character on editing repository
Key::Char(key) if ui_data.state == State::EditRepo => {
ui_data.info.set_text("Editing Repository");
ui_data.repo.handle_input(Key::Char(key));
}
//ignore all else input
_ => {}
},
Ok(UiEvent::RefreshOnNewData) | Err(_) => {}
}
}
terminal.clear()?;
Ok(())
}
}

352
src/ui/yaml_found.rs Normal file
View File

@ -0,0 +1,352 @@
use anyhow::Result;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::Terminal;
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use super::UiEvent;
use crate::error::Error;
use crate::repository;
use crate::widget::async_tag_list::{self, TagList};
use crate::widget::{info, repo_entry, service_switcher};
use crate::Opt;
pub struct Ui {
state: State,
repo: repo_entry::RepoEntry,
tags: TagList,
services: service_switcher::ServiceSwitcher,
details: crate::widget::details::Details,
info: info::Info,
}
#[derive(PartialEq, Clone)]
pub enum State {
EditRepo,
SelectTag,
SelectService,
}
impl std::fmt::Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
State::EditRepo => write!(f, "Edit repository"),
State::SelectTag => write!(f, "Select a tag"),
State::SelectService => write!(f, "Select a image"),
}
}
}
impl std::iter::Iterator for State {
type Item = Self;
fn next(&mut self) -> Option<Self::Item> {
match self {
State::EditRepo => *self = State::SelectTag,
State::SelectTag => *self = State::SelectService,
State::SelectService => *self = State::EditRepo,
}
Some(self.clone())
}
}
pub enum DeferredEvent {
Quit,
NewRepo(String),
LoadMoreTags,
}
impl Ui {
/// catch input and send them to core loop
pub fn wait_for_input(sender: mpsc::Sender<UiEvent>) -> Result<(), Error> {
let stdin = std::io::stdin();
for c in stdin.keys() {
sender.send(UiEvent::Input(c.unwrap()))?;
}
Ok(())
}
#[tokio::main]
pub async fn work_requests(
ui: &Arc<Mutex<Ui>>,
events: mpsc::Receiver<DeferredEvent>,
sender: mpsc::Sender<UiEvent>,
) -> Result<(), Error> {
use std::sync::atomic::Ordering;
let fetching_tags = Arc::new(std::sync::atomic::AtomicBool::new(false));
loop {
match events.recv() {
Ok(DeferredEvent::Quit) => break,
Ok(DeferredEvent::NewRepo(name)) => {
{
let mut ui = ui.lock().unwrap();
ui.tags = TagList::with_status("Fetching new tags...");
sender.send(UiEvent::RefreshOnNewData)?;
}
let list = TagList::with_repo_name(name).await;
let mut ui = ui.lock().unwrap();
ui.tags = list;
}
Ok(DeferredEvent::LoadMoreTags) if !fetching_tags.load(Ordering::Relaxed) => {
fetching_tags.store(true, Ordering::Relaxed);
let mut tags_copy = {
let mut ui = ui.lock().unwrap();
ui.info.set_text("Fetching more tags...");
sender.send(UiEvent::RefreshOnNewData)?;
ui.tags.clone()
};
let sender_copy = sender.clone();
let ui_copy = ui.clone();
let fetching_tags_copy = fetching_tags.clone();
std::thread::spawn(move || {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
tags_copy.load_next_page().await;
let mut ui = ui_copy.lock().unwrap();
//set position to the position of old TagList
//it may have changed since tag fetching has been invoked
tags_copy.set_cursor(ui.tags.get_cursor().clone());
ui.tags = tags_copy;
ui.details = ui.tags.create_detail_widget();
ui.info.set_text("Fetching tags done");
sender_copy.send(UiEvent::RefreshOnNewData).unwrap();
fetching_tags_copy.store(false, Ordering::Relaxed);
})
});
}
Ok(DeferredEvent::LoadMoreTags) => {
//do nothing, as we are fetching tags
}
Err(e) => {
let mut ui = ui.lock().unwrap();
ui.info.set_info(&e);
}
};
sender.send(UiEvent::RefreshOnNewData)?;
}
Ok(())
}
pub fn run(opt: &Opt, switcher: service_switcher::ServiceSwitcher) -> Result<()> {
let repo_id = opt.repo.as_deref();
let ui = Arc::new(Mutex::new(Ui {
state: State::SelectService,
repo: repo_entry::RepoEntry::new(repo_id),
tags: TagList::with_status("no tags"),
services: switcher,
details: crate::widget::details::Details::new(),
info: info::Info::new("Select image or edit Repository"),
}));
// spawn new thread that fetches information async
let (sender, receiver) = mpsc::channel();
let (deferred_sender, deferred_receiver) = mpsc::channel();
let ui_clone = ui.clone();
let sender2 = sender.clone();
std::thread::spawn(move || {
if let Err(e) = Self::work_requests(&ui_clone, deferred_receiver, sender2) {
let mut ui = ui_clone.lock().unwrap();
ui.info.set_info(&e);
}
});
//setup tui
let stdout = std::io::stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
//setup input thread
let ui_clone = ui.clone();
std::thread::spawn(move || {
if let Err(e) = Self::wait_for_input(sender) {
let mut ui = ui_clone.lock().unwrap();
ui.info.set_info(&e);
}
});
//core interaction loop
'core: loop {
//draw
let mut ui_data = ui.lock().unwrap();
terminal.draw(|rect| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length(10),
Constraint::Min(7),
Constraint::Length(2),
]
.as_ref(),
)
.split(rect.size());
let render_state = ui_data.state == State::SelectService;
let (file, state) = ui_data.services.render(render_state);
rect.render_stateful_widget(file, chunks[0], state);
let more_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Ratio(1, 3),
Constraint::Ratio(1, 3),
Constraint::Ratio(1, 3),
]
.as_ref(),
)
.split(chunks[1]);
rect.render_widget(
ui_data.repo.render(ui_data.state == State::EditRepo),
more_chunks[0],
);
let render_state = ui_data.state == State::SelectTag;
let (tags, state) = ui_data.tags.render(render_state);
rect.render_stateful_widget(tags, more_chunks[1], state);
rect.render_widget(ui_data.details.render(), more_chunks[2]);
rect.render_widget(ui_data.info.render(), chunks[2]);
})?;
drop(ui_data);
//handle events
//wait before locking
let event = receiver.recv();
let mut ui_data = ui.lock().unwrap();
match event {
//handling input
Ok(UiEvent::Input(key)) => match key {
//quit without saving
Key::Ctrl('q') | Key::Ctrl('c') => {
deferred_sender.send(DeferredEvent::Quit)?;
break 'core; //quit program without saving
}
//cycle widgets
Key::Char('\t') => {
ui_data.state.next();
let state = ui_data.state.clone();
ui_data.info.set_info(&state);
}
//save file
Key::Ctrl('s') => match ui_data.services.save() {
Err(e) => {
ui_data.info.set_info(&format!("{}", e));
continue;
}
Ok(_) => ui_data.info.set_text("Saved compose file"),
},
//refresh repository
Key::Ctrl('r') => {
ui_data.repo.confirm();
deferred_sender
.send(DeferredEvent::NewRepo(ui_data.repo.get()))
.unwrap();
}
//enter on selecting tags
Key::Char('\n') if ui_data.state == State::SelectTag => {
let mut repo = ui_data.repo.get();
let tag = match ui_data.tags.get_selected() {
Err(async_tag_list::Error::NextPageSelected) => continue,
Err(e) => {
ui_data.info.set_info(&format!("{}", e));
continue;
}
Ok(tag) => tag,
};
repo.push(':');
repo.push_str(&tag);
ui_data.services.change_current_line(repo);
}
//enter on editing repository
Key::Char('\n') if ui_data.state == State::EditRepo => {
ui_data.repo.confirm();
deferred_sender
.send(DeferredEvent::NewRepo(ui_data.repo.get()))
.unwrap();
}
//delete last char on repository
Key::Backspace if ui_data.state == State::EditRepo => {
ui_data.info.set_text("Editing Repository");
ui_data.repo.handle_input(Key::Backspace);
}
//moving up on selecting service
Key::Up | Key::Char('k')
if ui_data.state == State::SelectService
&& ui_data.services.find_previous_match() =>
{
match ui_data.services.extract_repo() {
Err(e) => ui_data.info.set_info(&format!("{}", e)),
Ok(s) => {
let repo = match repository::check_repo(&s) {
Err(e) => {
ui_data.info.set_info(&format!("{}", e));
continue;
}
Ok(s) => s,
};
ui_data.repo.set(repo.to_string());
deferred_sender
.send(DeferredEvent::NewRepo(ui_data.repo.get()))
.unwrap();
}
}
}
//moving down on selecting service
Key::Down | Key::Char('j')
if ui_data.state == State::SelectService
&& ui_data.services.find_next_match() =>
{
match ui_data.services.extract_repo() {
Err(e) => ui_data.info.set_info(&format!("{}", e)),
Ok(s) => {
let repo = match repository::check_repo(&s) {
Err(e) => {
ui_data.info.set_info(&format!("{}", e));
continue;
}
Ok(s) => s,
};
ui_data.repo.set(repo.to_string());
deferred_sender
.send(DeferredEvent::NewRepo(ui_data.repo.get()))
.unwrap();
}
}
}
//moving up on selecting tags
Key::Up | Key::Char('k') if ui_data.state == State::SelectTag => {
ui_data.tags.previous();
ui_data.details = ui_data.tags.create_detail_widget();
}
//moving down on selecting tags
Key::Down | Key::Char('j') if ui_data.state == State::SelectTag => {
if ui_data.tags.next().is_some() {
deferred_sender.send(DeferredEvent::LoadMoreTags).unwrap();
}
ui_data.details = ui_data.tags.create_detail_widget();
}
//append character on editing repository
Key::Char(key) if ui_data.state == State::EditRepo => {
ui_data.info.set_text("Editing Repository");
ui_data.repo.handle_input(Key::Char(key));
}
//ignore all else input
_ => {}
},
Ok(UiEvent::RefreshOnNewData) | Err(_) => {}
}
}
terminal.clear()?;
Ok(())
}
}

View File

@ -1,6 +1,5 @@
use std::fmt;
use termion::event::Key;
use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, List, ListState};
@ -22,6 +21,7 @@ impl fmt::Display for Error {
}
}
#[derive(Clone)]
enum Line {
Status(String),
Image(repository::Tag),
@ -38,6 +38,7 @@ impl fmt::Display for Line {
}
}
#[derive(Clone)]
pub struct TagList {
lines: Vec<Line>,
state: ListState,
@ -55,22 +56,22 @@ impl TagList {
}
/// list the tags of the repository if the input is valid
pub fn with_repo_name(repo: String) -> Self {
match repository::Repo::new(&repo) {
Ok(tags) => Self::with_tags(tags),
pub async fn with_repo_name(repo: String) -> Self {
match repository::Repo::new(&repo).await {
Ok(tags) => Self::with_tags(tags).await,
Err(_) => Self::with_status("input repo was not found"),
}
}
/// list the tags of the input
fn with_tags(mut tags: repository::Repo) -> Self {
async fn with_tags(mut tags: repository::Repo) -> Self {
let mut lines: Vec<Line> = tags
.get_tags()
.iter()
.map(|r| Line::Image(r.clone()))
.collect();
match tags.next_page() {
match tags.next_page().await {
None => (),
Some(new_tags) => {
lines.push(Line::NextPage(String::from("load more tags")));
@ -85,6 +86,14 @@ impl TagList {
}
}
pub fn set_cursor(&mut self, state: ListState) {
self.state = state;
}
pub fn get_cursor(&self) -> &ListState {
&self.state
}
pub fn render(&mut self, colored: bool) -> (List, &mut ListState) {
let border_style = if colored {
Style::default().fg(Color::Green)
@ -128,42 +137,21 @@ impl TagList {
}
}
pub fn handle_input(&mut self, key: termion::event::Key) {
match key {
Key::Down => self.next(),
Key::Up => self.previous(),
Key::Char('\n') => self.select(),
_ => (),
}
}
/// loads new tags when matching line is selected
fn select(&mut self) {
if let Some(i) = self.state.selected() {
if let Line::NextPage(_) = &self.lines[i] {
self.load_next_page()
}
}
}
pub fn get_selected(&mut self) -> Result<String, Error> {
match self.state.selected() {
None => Err(Error::NoneSelected),
Some(i) => match &self.lines[i] {
Line::Status(_) => Err(Error::SelectedStatus),
Line::Image(i) => Ok(i.get_name().to_string()),
Line::NextPage(_) => {
self.load_next_page();
Err(Error::NextPageSelected)
}
Line::NextPage(_) => Err(Error::NextPageSelected),
},
}
}
/// load new tags from the next page
fn load_next_page(&mut self) {
pub async fn load_next_page(&mut self) {
match &self.tags {
Some(tags) => match tags.next_page() {
Some(tags) => match tags.next_page().await {
None => (),
Some(new_tags) => {
//load new tags object
@ -173,19 +161,15 @@ impl TagList {
let next_page = self.lines.pop();
//add tags
match &self.tags {
None => (),
Some(tags) => {
if let Some(tags) = &self.tags {
for image in tags.get_tags().iter() {
self.lines.push(Line::Image(image.clone()));
}
}
}
//readd next page
match self.tags.as_ref().unwrap().next_page() {
None => (),
Some(_) => self.lines.push(next_page.unwrap()),
//readd next page item
if (self.tags.as_ref().unwrap().next_page().await).is_some() {
self.lines.push(next_page.unwrap());
}
}
},
@ -194,21 +178,29 @@ impl TagList {
}
/// select next tag
fn next(&mut self) {
/// returns Some when more tags need to be fetched otherwise None
pub fn next(&mut self) -> Option<()> {
if let Some(Line::Status(_)) = self.lines.get(0) {
return None;
}
match self.state.selected() {
None if !self.lines.is_empty() => self.state.select(Some(0)),
None => (),
Some(i) if i == self.lines.len() - 1 => self.state.select(Some(0)),
Some(i) if i == self.lines.len() - 2 => return Some(()),
// Some(i) if i == self.lines.len() - 2 => return self.load_next_page().await,
Some(i) => self.state.select(Some(i + 1)),
}
None
}
/// select previous tag
fn previous(&mut self) {
pub fn previous(&mut self) {
if let Some(Line::Status(_)) = self.lines.get(0) {
return;
}
match self.state.selected() {
None if !self.lines.is_empty() => self.state.select(Some(self.lines.len())),
None => (),
Some(i) if i == 0 => self.state.select(Some(self.lines.len() - 1)),
None => self.state.select(Some(0)),
Some(i) if i == 0 => self.state.select(Some(self.lines.len() - 2)),
Some(i) => self.state.select(Some(i - 1)),
}
}

View File

@ -26,7 +26,7 @@ impl Details {
let mut lines = vec![format!("{:^10}|{:^6}|{:^6}", "ARCH", "OS", "SIZE")];
for d in &self.details {
lines.push(format!(
"{:^10}|{:^6}|{:^6}MB",
"{:>10}|{:>6}|{:>6} MB",
format!(
"{}{}",
d.arch.clone().unwrap_or_default(),

View File

@ -11,7 +11,7 @@ impl Info {
Self {
info: String::from(info),
keys: String::from(
"Tab Cycle widgets C-s Save C-r Reload C-q Quit ↑ ↓ Select tags or image line Return Select current selection",
"Tab Cycle widgets C-s Save C-r Reload C-q Quit ↑ ↓ Select tags or image line Return Select",
),
}
}

View File

@ -1,5 +1,5 @@
pub mod async_tag_list;
pub mod details;
pub mod info;
pub mod repo_entry;
pub mod service_switcher;
pub mod tag_list;

View File

@ -12,7 +12,7 @@ pub struct RepoEntry {
impl RepoEntry {
pub fn new(text: Option<&str>) -> Self {
let default_text = "enter a repository here or select one from file widget";
let default_text = "edit me or select a repository";
Self {
text: String::from(text.unwrap_or(default_text)),
old_text: String::from(text.unwrap_or(default_text)),