Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
devel:tools:scm
forgejo-cli
forgejo-cli-0.0.4+git13.0a30d14.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File forgejo-cli-0.0.4+git13.0a30d14.obscpio of Package forgejo-cli
07070100000000000081A4000000000000000000000001663A7FD900000008000000000000000000000000000000000000002B00000000forgejo-cli-0.0.4+git13.0a30d14/.gitignore/target 07070100000001000041ED000000000000000000000002663A7FD900000000000000000000000000000000000000000000002C00000000forgejo-cli-0.0.4+git13.0a30d14/.woodpecker07070100000002000081A4000000000000000000000001663A7FD9000000DC000000000000000000000000000000000000003600000000forgejo-cli-0.0.4+git13.0a30d14/.woodpecker/check.ymlwhen: - event: manual - event: pull_request steps: check: image: rust commands: - cargo check check-fmt: image: rust commands: - rustup component add rustfmt - cargo fmt --check 07070100000003000081A4000000000000000000000001663A7FD90000072B000000000000000000000000000000000000003700000000forgejo-cli-0.0.4+git13.0a30d14/.woodpecker/deploy.ymlwhen: - event: tag steps: compile-linux: image: rust:latest commands: - rustup target add x86_64-unknown-linux-gnu - cargo build --target=x86_64-unknown-linux-gnu --release - strip target/x86_64-unknown-linux-gnu/release/fj compile-windows: image: rust:latest commands: - rustup target add x86_64-pc-windows-gnu - apt update - apt install gcc-mingw-w64-x86-64 -y - cargo build --target=x86_64-pc-windows-gnu --release - strip target/x86_64-pc-windows-gnu/release/fj.exe zip: image: debian:12 commands: - apt update - apt install zip -y - cd target/x86_64-pc-windows-gnu/release - zip ../../../forgejo-cli-windows.zip fj.exe - cd ../../.. - gzip -c target/x86_64-unknown-linux-gnu/release/fj > forgejo-cli-linux.gz deploy-container: image: gcr.io/kaniko-project/executor:debug commands: - export FORGE_HOST=$(echo $CI_FORGE_URL | sed -E 's_^https?://__') - export AUTH="$(echo -n $CI_REPO_OWNER:$TOKEN | base64)" - echo "{\"auths\":{\"$FORGE_HOST\":{\"auth\":\"$AUTH\"}}}" > "/kaniko/.docker/config.json" - export CONTAINER_OWNER=$(echo $CI_REPO_OWNER | awk '{print tolower($0)}') - executor --context ./ --dockerfile ./Dockerfile --destination "$FORGE_HOST/$CONTAINER_OWNER/forgejo-cli:latest" secrets: [ token ] release: image: codeberg.org/cyborus/forgejo-cli:latest pull: true commands: - export FORGE_HOST=$(echo $CI_FORGE_URL | sed -E 's_^https?://__') - fj auth add-key $FORGE_HOST $CI_REPO_OWNER $TOKEN - fj release --repo $CI_REPO_URL asset create $CI_COMMIT_TAG forgejo-cli-windows.zip - fj release --repo $CI_REPO_URL asset create $CI_COMMIT_TAG forgejo-cli-linux.gz - fj auth logout $FORGE_HOST secrets: [ token ] 07070100000004000081A4000000000000000000000001663A7FD90000AFA9000000000000000000000000000000000000002B00000000forgejo-cli-0.0.4+git13.0a30d14/Cargo.lock# 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 = "anstream" version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "auth-git2" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51bd0e4592409df8631ca807716dc1e5caafae5d01ce0157c966c71c7e49c3c" dependencies = [ "dirs", "git2", "terminal-prompt", ] [[package]] name = "autocfg" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" dependencies = [ "jobserver", "libc", "once_cell", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", ] [[package]] name = "directories" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ "dirs-sys", ] [[package]] name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", "redox_users", "windows-sys 0.48.0", ] [[package]] name = "encoding_rs" version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys 0.52.0", ] [[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 = "fastrand" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fj" version = "0.0.4" dependencies = [ "auth-git2", "clap", "directories", "eyre", "forgejo-api", "futures", "git2", "open", "serde", "serde_json", "soft_assert", "time", "tokio", "url", "uuid", ] [[package]] name = "fnv" 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 = "forgejo-api" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0452961a1435fdf127da1a1ba13cb840efe26bf6169bfd8d6dafecdd3e0eafa" dependencies = [ "base64ct", "bytes", "reqwest", "serde", "serde_json", "soft_assert", "thiserror", "time", "tokio", "url", "zeroize", ] [[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 = "futures" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git2" version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044" dependencies = [ "bitflags 1.3.2", "libc", "libgit2-sys", "log", "openssl-probe", "openssl-sys", "url", ] [[package]] name = "h2" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "http" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", "pin-project-lite", ] [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", "want", ] [[package]] name = "hyper-tls" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", "hyper", "native-tls", "tokio", "tokio-native-tls", ] [[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 = "indexmap" version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-docker" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" dependencies = [ "once_cell", ] [[package]] name = "is-wsl" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" dependencies = [ "is-docker", "once_cell", ] [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] [[package]] name = "js-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[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.154" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libgit2-sys" version = "0.15.2+1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a80df2e11fb4a61f4ba2ab42dbe7f74468da143f1a75c74e11dee7c813f694fa" dependencies = [ "cc", "libc", "libssh2-sys", "libz-sys", "openssl-sys", "pkg-config", ] [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.5.0", "libc", ] [[package]] name = "libssh2-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" dependencies = [ "cc", "libc", "libz-sys", "openssl-sys", "pkg-config", "vcpkg", ] [[package]] name = "libz-sys" version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" dependencies = [ "mime", "unicase", ] [[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", "wasi", "windows-sys 0.48.0", ] [[package]] name = "native-tls" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[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 = "open" version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "449f0ff855d85ddbf1edd5b646d65249ead3f5e422aaa86b7d2d0b049b103e32" dependencies = [ "is-wsl", "libc", "pathdiff", ] [[package]] name = "openssl" version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "parking_lot" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.5", ] [[package]] name = "pathdiff" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[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.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ "bitflags 2.5.0", ] [[package]] name = "redox_users" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", "thiserror", ] [[package]] name = "reqwest" version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", "hyper", "hyper-tls", "ipnet", "js-sys", "log", "mime", "mime_guess", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "winreg", ] [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls-pemfile" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64", ] [[package]] name = "ryu" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "schannel" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "soft_assert" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5097ec7ea7218135541ad96348f1441d0c616537dd4ed9c47205920c35d7d97" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "system-configuration" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", "rustix", "windows-sys 0.52.0", ] [[package]] name = "terminal-prompt" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572818b3472910acbd5dff46a3413715c18e934b071ab2ba464a7b2c2af16376" dependencies = [ "libc", "winapi", ] [[package]] name = "thiserror" version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", ] [[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 = "tokio" version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", "tracing", ] [[package]] name = "tower-service" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[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", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicase" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[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 = "url" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", ] [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[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.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] [[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 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.5", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm 0.52.5", "windows_aarch64_msvc 0.52.5", "windows_i686_gnu 0.52.5", "windows_i686_gnullvm", "windows_i686_msvc 0.52.5", "windows_x86_64_gnu 0.52.5", "windows_x86_64_gnullvm 0.52.5", "windows_x86_64_msvc 0.52.5", ] [[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_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[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_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] name = "windows_i686_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[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_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[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_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winreg" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] [[package]] name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" 07070100000005000081A4000000000000000000000001663A7FD900000271000000000000000000000000000000000000002B00000000forgejo-cli-0.0.4+git13.0a30d14/Cargo.toml[package] name = "fj" version = "0.0.4" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] auth-git2 = "0.5.3" clap = { version = "4.3.11", features = ["derive"] } directories = "5.0.1" eyre = "0.6.8" forgejo-api = "0.2.0" futures = "0.3.28" git2 = "0.17.2" open = "5.0.0" serde = { version = "1.0.170", features = ["derive"] } serde_json = "1.0.100" soft_assert = "0.1.1" time = { version = "0.3.30", features = ["formatting"] } tokio = { version = "1.29.1", features = ["full"] } url = "2.4.0" uuid = { version = "1.5.0", features = ["v4"] } 07070100000006000081A4000000000000000000000001663A7FD90000008E000000000000000000000000000000000000002B00000000forgejo-cli-0.0.4+git13.0a30d14/DockerfileFROM debian:12 RUN apt update RUN apt install libssl-dev ca-certificates -y COPY target/x86_64-unknown-linux-gnu/release/fj /usr/local/bin/fj 07070100000007000081A4000000000000000000000001663A7FD900002C5D000000000000000000000000000000000000002F00000000forgejo-cli-0.0.4+git13.0a30d14/LICENSE-APACHE Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 07070100000008000081A4000000000000000000000001663A7FD90000042D000000000000000000000000000000000000002C00000000forgejo-cli-0.0.4+git13.0a30d14/LICENSE-MITMIT License Copyright (c) [year] [fullname] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 07070100000009000081A4000000000000000000000001663A7FD900000835000000000000000000000000000000000000002A00000000forgejo-cli-0.0.4+git13.0a30d14/README.md# forgejo-cli CLI tool for interacting with Forgejo ## Installation ### Pre-built Pre-built binaries are available for `x86_64` Windows and Linux (GNU) on the [releases tab](https://codeberg.org/Cyborus/forgejo-cli/releases/latest). ### From source Install with `cargo install` ``` # Latest version cargo install --git https://codeberg.org/Cyborus/forgejo-cli.git --tag v0.0.4 # From `main` cargo install --git https://codeberg.org/Cyborus/forgejo-cli.git --branch main ``` ### OCI Container `forgejo-cli` is available as an OCI container for use in CI, at `codeberg.org/cyborus/forgejo-cli:latest` ## Usage ### Instance-specific aliases While you can just use the `fj` binary directly, it can be useful to alias it with the `--host` flag set, to create shorthands for certain instances. ```bash # For example, a `cb` command for interacting with codeberg alias cb="fj --host codeberg.org" # Or disroot alias dr="fj --host git.disroot.org" # Or any other instance you want! # And the alias name can be whatever, as long as the `--host` flag is set. ``` Now, when you reference a repository such as `forgejo/forgejo`, it will implicitly get it from whichever alias you used! ``` $ cb repo info forgejo/forgejo forgejo/forgejo > Beyond coding. We forge. Primary language is Go # etc... ``` When using `fj` directly, you'd have to use a URL to access it. ``` $ fj repo info codeberg.org/forgejo/forgejo forgejo/forgejo > Beyond coding. We forge. Primary language is Go # etc... # Notice the "dr", trying to access Disroot, still works when you specify Codeberg in the repository name! $ dr repo info codeberg.org/forgejo/forgejo forgejo/forgejo > Beyond coding. We forge. Primary language is Go # etc... ``` ## Licensing This project is licensed under either [Apache License Version 2.0](LICENSE-APACHE) or [MIT License](LICENSE-MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 0707010000000A000041ED000000000000000000000002663A7FD900000000000000000000000000000000000000000000002400000000forgejo-cli-0.0.4+git13.0a30d14/src0707010000000B000081A4000000000000000000000001663A7FD90000079D000000000000000000000000000000000000002C00000000forgejo-cli-0.0.4+git13.0a30d14/src/auth.rsuse clap::Subcommand; #[derive(Subcommand, Clone, Debug)] pub enum AuthCommand { Login, Logout { host: String, }, AddKey { /// The domain name of the forgejo instance. host: String, /// The user that the key is associated with user: String, /// The key to add. If not present, the key will be read in from stdin. key: Option<String>, }, List, } impl AuthCommand { pub async fn run(self, keys: &mut crate::KeyInfo) -> eyre::Result<()> { match self { AuthCommand::Login => { todo!(); // let user = readline("username: ").await?; // let pass = readline("password: ").await?; } AuthCommand::Logout { host } => { let info_opt = keys.hosts.remove(&host); if let Some(info) = info_opt { eprintln!("signed out of {}@{}", &info.username(), host); } else { eprintln!("already not signed in to {host}"); } } AuthCommand::AddKey { host, user, key } => { let key = match key { Some(key) => key, None => crate::readline("new key: ").await?.trim().to_string(), }; if keys.hosts.get(&user).is_none() { keys.hosts .insert(host, crate::keys::LoginInfo::new(user, key)); } else { println!("key for {} already exists", host); } } AuthCommand::List => { if keys.hosts.is_empty() { println!("No logins."); } for (host_url, login_info) in &keys.hosts { println!("{}@{}", login_info.username(), host_url); } } } Ok(()) } } 0707010000000C000081A4000000000000000000000001663A7FD900003933000000000000000000000000000000000000002E00000000forgejo-cli-0.0.4+git13.0a30d14/src/issues.rsuse clap::{Args, Subcommand}; use eyre::{eyre, OptionExt}; use forgejo_api::structs::{ Comment, CreateIssueCommentOption, CreateIssueOption, EditIssueOption, IssueGetCommentsQuery, }; use forgejo_api::Forgejo; use crate::repo::{RepoInfo, RepoName}; #[derive(Args, Clone, Debug)] pub struct IssueCommand { #[clap(long, short = 'R')] remote: Option<String>, #[clap(long, short)] repo: Option<String>, #[clap(subcommand)] command: IssueSubcommand, } #[derive(Subcommand, Clone, Debug)] pub enum IssueSubcommand { Create { title: String, #[clap(long)] body: Option<String>, }, Edit { issue: u64, #[clap(subcommand)] command: EditCommand, }, Comment { issue: u64, body: Option<String>, }, Close { issue: u64, #[clap(long, short)] with_msg: Option<Option<String>>, }, Search { query: Option<String>, #[clap(long, short)] labels: Option<String>, #[clap(long, short)] creator: Option<String>, #[clap(long, short)] assignee: Option<String>, #[clap(long, short)] state: Option<State>, }, View { id: u64, #[clap(subcommand)] command: Option<ViewCommand>, }, Browse { id: Option<u64>, }, } #[derive(clap::ValueEnum, Clone, Copy, Debug)] pub enum State { Open, Closed, } impl From<State> for forgejo_api::structs::IssueListIssuesQueryState { fn from(value: State) -> Self { match value { State::Open => forgejo_api::structs::IssueListIssuesQueryState::Open, State::Closed => forgejo_api::structs::IssueListIssuesQueryState::Closed, } } } #[derive(Subcommand, Clone, Debug)] pub enum EditCommand { Title { new_title: Option<String>, }, Body { new_body: Option<String>, }, Comment { idx: usize, new_body: Option<String>, }, } #[derive(Subcommand, Clone, Debug)] pub enum ViewCommand { Body, Comment { idx: usize }, Comments, } impl IssueCommand { pub async fn run(self, keys: &crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> { use IssueSubcommand::*; let repo = RepoInfo::get_current(host_name, self.repo.as_deref(), self.remote.as_deref())?; let api = keys.get_api(repo.host_url())?; let repo = repo .name() .ok_or_eyre("couldn't get repo name, try specifying with --repo")?; match self.command { Create { title, body } => create_issue(&repo, &api, title, body).await?, View { id, command } => match command.unwrap_or(ViewCommand::Body) { ViewCommand::Body => view_issue(&repo, &api, id).await?, ViewCommand::Comment { idx } => view_comment(&repo, &api, id, idx).await?, ViewCommand::Comments => view_comments(&repo, &api, id).await?, }, Search { query, labels, creator, assignee, state, } => view_issues(&repo, &api, query, labels, creator, assignee, state).await?, Edit { issue, command } => match command { EditCommand::Title { new_title } => { edit_title(&repo, &api, issue, new_title).await? } EditCommand::Body { new_body } => edit_body(&repo, &api, issue, new_body).await?, EditCommand::Comment { idx, new_body } => { edit_comment(&repo, &api, issue, idx, new_body).await? } }, Close { issue, with_msg } => close_issue(&repo, &api, issue, with_msg).await?, Browse { id } => browse_issue(&repo, &api, id).await?, Comment { issue, body } => add_comment(&repo, &api, issue, body).await?, } Ok(()) } } async fn create_issue( repo: &RepoName, api: &Forgejo, title: String, body: Option<String>, ) -> eyre::Result<()> { let body = match body { Some(body) => body, None => { let mut body = String::new(); crate::editor(&mut body, Some("md")).await?; body } }; let issue = api .issue_create_issue( repo.owner(), repo.name(), CreateIssueOption { body: Some(body), title, assignee: None, assignees: None, closed: None, due_date: None, labels: None, milestone: None, r#ref: None, }, ) .await?; let number = issue .number .ok_or_else(|| eyre::eyre!("issue does not have number"))?; let title = issue .title .as_ref() .ok_or_else(|| eyre::eyre!("issue does not have title"))?; eprintln!("created issue #{}: {}", number, title); Ok(()) } async fn view_issue(repo: &RepoName, api: &Forgejo, id: u64) -> eyre::Result<()> { let issue = api.issue_get_issue(repo.owner(), repo.name(), id).await?; let title = issue .title .as_ref() .ok_or_else(|| eyre::eyre!("issue does not have title"))?; let user = issue .user .as_ref() .ok_or_else(|| eyre::eyre!("issue does not have creator"))?; let username = user .login .as_ref() .ok_or_else(|| eyre::eyre!("user does not have login"))?; println!("#{}: {}", id, title); println!("By {}", username); if let Some(body) = &issue.body { println!(); println!("{}", body); } Ok(()) } async fn view_issues( repo: &RepoName, api: &Forgejo, query_str: Option<String>, labels: Option<String>, creator: Option<String>, assignee: Option<String>, state: Option<State>, ) -> eyre::Result<()> { let labels = labels .map(|s| s.split(',').map(|s| s.to_string()).collect::<Vec<_>>()) .unwrap_or_default(); let query = forgejo_api::structs::IssueListIssuesQuery { q: query_str, labels: Some(labels.join(",")), created_by: creator, assigned_by: assignee, state: state.map(|s| s.into()), r#type: None, milestones: None, since: None, before: None, mentioned_by: None, page: None, limit: None, }; let issues = api .issue_list_issues(repo.owner(), repo.name(), query) .await?; if issues.len() == 1 { println!("1 issue"); } else { println!("{} issues", issues.len()); } for issue in issues { let number = issue .number .ok_or_else(|| eyre::eyre!("issue does not have number"))?; let title = issue .title .as_ref() .ok_or_else(|| eyre::eyre!("issue does not have title"))?; let user = issue .user .as_ref() .ok_or_else(|| eyre::eyre!("issue does not have creator"))?; let username = user .login .as_ref() .ok_or_else(|| eyre::eyre!("user does not have login"))?; println!("#{}: {} (by {})", number, title, username); } Ok(()) } async fn view_comment(repo: &RepoName, api: &Forgejo, id: u64, idx: usize) -> eyre::Result<()> { let query = IssueGetCommentsQuery { since: None, before: None, }; let comments = api .issue_get_comments(repo.owner(), repo.name(), id, query) .await?; let comment = comments .get(idx) .ok_or_else(|| eyre!("comment {idx} doesn't exist"))?; print_comment(&comment)?; Ok(()) } async fn view_comments(repo: &RepoName, api: &Forgejo, id: u64) -> eyre::Result<()> { let query = IssueGetCommentsQuery { since: None, before: None, }; let comments = api .issue_get_comments(repo.owner(), repo.name(), id, query) .await?; for comment in comments { print_comment(&comment)?; } Ok(()) } fn print_comment(comment: &Comment) -> eyre::Result<()> { let body = comment .body .as_ref() .ok_or_else(|| eyre::eyre!("comment does not have body"))?; let user = comment .user .as_ref() .ok_or_else(|| eyre::eyre!("comment does not have user"))?; let username = user .login .as_ref() .ok_or_else(|| eyre::eyre!("user does not have login"))?; println!("{} said:", username); println!("{}", body); let assets = comment .assets .as_ref() .ok_or_else(|| eyre::eyre!("comment does not have assets"))?; if !assets.is_empty() { println!("({} attachments)", assets.len()); } Ok(()) } async fn browse_issue(repo: &RepoName, api: &Forgejo, id: Option<u64>) -> eyre::Result<()> { match id { Some(id) => { let issue = api.issue_get_issue(repo.owner(), repo.name(), id).await?; let html_url = issue .html_url .as_ref() .ok_or_else(|| eyre::eyre!("issue does not have html_url"))?; open::that(html_url.as_str())?; } None => { let repo = api.repo_get(repo.owner(), repo.name()).await?; let html_url = repo .html_url .as_ref() .ok_or_else(|| eyre::eyre!("issue does not have html_url"))?; open::that(format!("{}/issues", html_url))?; } } Ok(()) } async fn add_comment( repo: &RepoName, api: &Forgejo, issue: u64, body: Option<String>, ) -> eyre::Result<()> { let body = match body { Some(body) => body, None => { let mut body = String::new(); crate::editor(&mut body, Some("md")).await?; body } }; api.issue_create_comment( repo.owner(), repo.name(), issue, forgejo_api::structs::CreateIssueCommentOption { body, updated_at: None, }, ) .await?; Ok(()) } async fn edit_title( repo: &RepoName, api: &Forgejo, issue: u64, new_title: Option<String>, ) -> eyre::Result<()> { let new_title = match new_title { Some(s) => s, None => { let issue_info = api .issue_get_issue(repo.owner(), repo.name(), issue) .await?; let mut title = issue_info .title .ok_or_else(|| eyre::eyre!("issue does not have title"))?; crate::editor(&mut title, Some("md")).await?; title } }; if new_title.is_empty() { eyre::bail!("title cannot be empty"); } if new_title.contains('\n') { eyre::bail!("title cannot contain newlines"); } api.issue_edit_issue( repo.owner(), repo.name(), issue, forgejo_api::structs::EditIssueOption { title: Some(new_title.trim().to_owned()), assignee: None, assignees: None, body: None, due_date: None, milestone: None, r#ref: None, state: None, unset_due_date: None, updated_at: None, }, ) .await?; Ok(()) } async fn edit_body( repo: &RepoName, api: &Forgejo, issue: u64, new_body: Option<String>, ) -> eyre::Result<()> { let new_body = match new_body { Some(s) => s, None => { let issue_info = api .issue_get_issue(repo.owner(), repo.name(), issue) .await?; let mut body = issue_info .body .ok_or_else(|| eyre::eyre!("issue does not have body"))?; crate::editor(&mut body, Some("md")).await?; body } }; api.issue_edit_issue( repo.owner(), repo.name(), issue, forgejo_api::structs::EditIssueOption { body: Some(new_body), assignee: None, assignees: None, due_date: None, milestone: None, r#ref: None, state: None, title: None, unset_due_date: None, updated_at: None, }, ) .await?; Ok(()) } async fn edit_comment( repo: &RepoName, api: &Forgejo, issue: u64, idx: usize, new_body: Option<String>, ) -> eyre::Result<()> { let comments = api .issue_get_comments( repo.owner(), repo.name(), issue, IssueGetCommentsQuery { since: None, before: None, }, ) .await?; let comment = comments .get(idx) .ok_or_else(|| eyre!("comment not found"))?; let new_body = match new_body { Some(s) => s, None => { let mut body = comment .body .clone() .ok_or_else(|| eyre::eyre!("issue does not have body"))?; crate::editor(&mut body, Some("md")).await?; body } }; let id = comment .id .ok_or_else(|| eyre::eyre!("comment does not have id"))?; api.issue_edit_comment( repo.owner(), repo.name(), id, forgejo_api::structs::EditIssueCommentOption { body: new_body, updated_at: None, }, ) .await?; Ok(()) } async fn close_issue( repo: &RepoName, api: &Forgejo, issue: u64, message: Option<Option<String>>, ) -> eyre::Result<()> { if let Some(message) = message { let body = match message { Some(m) => m, None => { let mut s = String::new(); crate::editor(&mut s, Some("md")).await?; s } }; let opt = CreateIssueCommentOption { body, updated_at: None, }; api.issue_create_comment(repo.owner(), repo.name(), issue, opt) .await?; } let edit = EditIssueOption { state: Some("closed".into()), assignee: None, assignees: None, body: None, due_date: None, milestone: None, r#ref: None, title: None, unset_due_date: None, updated_at: None, }; api.issue_edit_issue(repo.owner(), repo.name(), issue, edit) .await?; Ok(()) } 0707010000000D000081A4000000000000000000000001663A7FD9000009E0000000000000000000000000000000000000002C00000000forgejo-cli-0.0.4+git13.0a30d14/src/keys.rsuse eyre::eyre; use std::{collections::BTreeMap, io::ErrorKind}; use tokio::io::AsyncWriteExt; use url::Url; #[derive(serde::Serialize, serde::Deserialize, Clone, Default)] pub struct KeyInfo { pub hosts: BTreeMap<String, LoginInfo>, } impl KeyInfo { pub async fn load() -> eyre::Result<Self> { let path = directories::ProjectDirs::from("", "Cyborus", "forgejo-cli") .ok_or_else(|| eyre!("Could not find data directory"))? .data_dir() .join("keys.json"); let json = tokio::fs::read(path).await; let this = match json { Ok(x) => serde_json::from_slice::<Self>(&x)?, Err(e) if e.kind() == ErrorKind::NotFound => { eprintln!("keys file not found, creating"); Self::default() } Err(e) => return Err(e.into()), }; Ok(this) } pub async fn save(&self) -> eyre::Result<()> { let json = serde_json::to_vec_pretty(self)?; let dirs = directories::ProjectDirs::from("", "Cyborus", "forgejo-cli") .ok_or_else(|| eyre!("Could not find data directory"))?; let path = dirs.data_dir(); tokio::fs::create_dir_all(path).await?; tokio::fs::File::create(path.join("keys.json")) .await? .write_all(&json) .await?; Ok(()) } pub fn get_login(&self, url: &Url) -> eyre::Result<&LoginInfo> { let host_str = url .host_str() .ok_or_else(|| eyre!("remote url does not have host"))?; let domain = if let Some(port) = url.port() { format!("{}:{}", host_str, port) } else { host_str.to_owned() }; let login_info = self .hosts .get(&domain) .ok_or_else(|| eyre!("not signed in to {domain}"))?; Ok(login_info) } pub fn get_api(&self, url: &Url) -> eyre::Result<forgejo_api::Forgejo> { self.get_login(url)?.api_for(url).map_err(Into::into) } } #[derive(serde::Serialize, serde::Deserialize, Clone, Default)] pub struct LoginInfo { name: String, key: String, } impl LoginInfo { pub fn new(name: String, key: String) -> Self { Self { name, key } } pub fn username(&self) -> &str { &self.name } pub fn api_for(&self, url: &Url) -> Result<forgejo_api::Forgejo, forgejo_api::ForgejoError> { forgejo_api::Forgejo::new(forgejo_api::Auth::Token(&self.key), url.clone()) } } 0707010000000E000081A4000000000000000000000001663A7FD900001A95000000000000000000000000000000000000002C00000000forgejo-cli-0.0.4+git13.0a30d14/src/main.rsuse std::io::IsTerminal; use clap::{Parser, Subcommand}; use eyre::{eyre, Context, OptionExt}; use tokio::io::AsyncWriteExt; mod keys; use keys::*; mod auth; mod issues; mod release; mod repo; #[derive(Parser, Debug)] pub struct App { #[clap(long, short = 'H')] host: Option<String>, #[clap(long)] style: Option<Style>, #[clap(subcommand)] command: Command, } #[derive(Subcommand, Clone, Debug)] pub enum Command { #[clap(subcommand)] Repo(repo::RepoCommand), Issue(issues::IssueCommand), #[command(name = "whoami")] WhoAmI { #[clap(long, short)] remote: Option<String>, }, #[clap(subcommand)] Auth(auth::AuthCommand), Release(release::ReleaseCommand), } #[tokio::main] async fn main() -> eyre::Result<()> { let args = App::parse(); let _ = SPECIAL_RENDER.set(SpecialRender::new(args.style.unwrap_or_default())); let mut keys = KeyInfo::load().await?; let host_name = args.host.as_deref(); // let remote = repo::RepoInfo::get_current(host_name, remote_name)?; match args.command { Command::Repo(subcommand) => subcommand.run(&keys, host_name).await?, Command::Issue(subcommand) => subcommand.run(&keys, host_name).await?, Command::WhoAmI { remote } => { let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref()) .wrap_err("could not find host, try specifying with --host")? .host_url() .clone(); let name = keys.get_login(&url)?.username(); let host = url .host_str() .ok_or_eyre("instance url does not have host")?; if url.path() == "/" || url.path().is_empty() { println!("currently signed in to {name}@{host}"); } else { println!("currently signed in to {name}@{host}{}", url.path()); } } Command::Auth(subcommand) => subcommand.run(&mut keys).await?, Command::Release(subcommand) => subcommand.run(&mut keys, host_name).await?, } keys.save().await?; Ok(()) } async fn readline(msg: &str) -> eyre::Result<String> { print!("{msg}"); tokio::io::stdout().flush().await?; tokio::task::spawn_blocking(|| { let mut input = String::new(); std::io::stdin().read_line(&mut input)?; Ok(input) }) .await? } async fn editor(contents: &mut String, ext: Option<&str>) -> eyre::Result<()> { let editor = std::path::PathBuf::from( std::env::var_os("EDITOR").ok_or_else(|| eyre!("unable to locate editor"))?, ); let (mut file, path) = tempfile(ext).await?; file.write_all(contents.as_bytes()).await?; drop(file); // Closure acting as a try/catch block so that the temp file is deleted even // on errors let res = (|| async { eprint!("waiting on editor\r"); let flags = get_editor_flags(&editor); let status = tokio::process::Command::new(editor) .args(flags) .arg(&path) .status() .await?; if !status.success() { eyre::bail!("editor exited unsuccessfully"); } *contents = tokio::fs::read_to_string(&path).await?; eprint!(" \r"); Ok(()) })() .await; tokio::fs::remove_file(path).await?; res?; Ok(()) } fn get_editor_flags(editor_path: &std::path::Path) -> &'static [&'static str] { let editor_name = match editor_path.file_stem().and_then(|s| s.to_str()) { Some(name) => name, None => return &[], }; if editor_name == "code" { return &["--wait"]; } &[] } async fn tempfile(ext: Option<&str>) -> tokio::io::Result<(tokio::fs::File, std::path::PathBuf)> { let filename = uuid::Uuid::new_v4(); let mut path = std::env::temp_dir().join(filename.to_string()); if let Some(ext) = ext { path.set_extension(ext); } let file = tokio::fs::OpenOptions::new() .create(true) .read(true) .write(true) .open(&path) .await?; Ok((file, path)) } use std::sync::OnceLock; static SPECIAL_RENDER: OnceLock<SpecialRender> = OnceLock::new(); fn special_render() -> &'static SpecialRender { SPECIAL_RENDER .get() .expect("attempted to get special characters before that was initialized") } #[derive(clap::ValueEnum, Clone, Copy, Debug, Default)] enum Style { /// Use special characters, and colors. #[default] Fancy, /// No special characters and no colors. Always used in non-terminal contexts (i.e. pipes) Minimal, } struct SpecialRender { dash: char, bullet: char, body_prefix: char, red: &'static str, bright_red: &'static str, green: &'static str, bright_green: &'static str, blue: &'static str, bright_blue: &'static str, cyan: &'static str, bright_cyan: &'static str, yellow: &'static str, bright_yellow: &'static str, magenta: &'static str, bright_magenta: &'static str, black: &'static str, dark_grey: &'static str, light_grey: &'static str, white: &'static str, reset: &'static str, } impl SpecialRender { fn new(display: Style) -> Self { let is_tty = std::io::stdout().is_terminal(); match display { _ if !is_tty => Self::minimal(), Style::Fancy => Self::fancy(), Style::Minimal => Self::minimal(), } } fn fancy() -> Self { Self { dash: '—', bullet: '•', body_prefix: '▌', red: "\x1b[31m", bright_red: "\x1b[91m", green: "\x1b[32m", bright_green: "\x1b[92m", blue: "\x1b[34m", bright_blue: "\x1b[94m", cyan: "\x1b[36m", bright_cyan: "\x1b[96m", yellow: "\x1b[33m", bright_yellow: "\x1b[93m", magenta: "\x1b[35m", bright_magenta: "\x1b[95m", black: "\x1b[30m", dark_grey: "\x1b[90m", light_grey: "\x1b[37m", white: "\x1b[97m", reset: "\x1b[0m", } } fn minimal() -> Self { Self { dash: '-', bullet: '-', body_prefix: '>', red: "", bright_red: "", green: "", bright_green: "", blue: "", bright_blue: "", cyan: "", bright_cyan: "", yellow: "", bright_yellow: "", magenta: "", bright_magenta: "", black: "", dark_grey: "", light_grey: "", white: "", reset: "", } } } 0707010000000F000081A4000000000000000000000001663A7FD900004554000000000000000000000000000000000000002F00000000forgejo-cli-0.0.4+git13.0a30d14/src/release.rsuse clap::{Args, Subcommand}; use eyre::{bail, eyre, OptionExt}; use forgejo_api::{ structs::{RepoCreateReleaseAttachmentQuery, RepoListReleasesQuery}, Forgejo, }; use tokio::io::AsyncWriteExt; use crate::{ keys::KeyInfo, repo::{RepoInfo, RepoName}, SpecialRender, }; #[derive(Args, Clone, Debug)] pub struct ReleaseCommand { #[clap(long, short = 'R')] remote: Option<String>, #[clap(long, short)] repo: Option<String>, #[clap(subcommand)] command: ReleaseSubcommand, } #[derive(Subcommand, Clone, Debug)] pub enum ReleaseSubcommand { Create { name: String, #[clap(long, short = 'T')] /// Create a new cooresponding tag for this release. Defaults to release's name. create_tag: Option<Option<String>>, #[clap(long, short = 't')] /// Pre-existing tag to use /// /// If you need to create a new tag for this release, use `--create-tag` tag: Option<String>, #[clap( long, short, help = "Include a file as an attachment", long_help = "Include a file as an attachment `--attach=<FILE>` will set the attachment's name to the file name `--attach=<FILE>:<ASSET>` will use the provided name for the attachment" )] attach: Vec<String>, #[clap(long, short)] /// Text of the release body. /// /// Using this flag without an argument will open your editor. body: Option<Option<String>>, #[clap(long, short = 'B')] branch: Option<String>, #[clap(long, short)] draft: bool, #[clap(long, short)] prerelease: bool, }, Edit { name: String, #[clap(long, short = 'n')] rename: Option<String>, #[clap(long, short = 't')] /// Corresponding tag for this release. tag: Option<String>, #[clap(long, short)] /// Text of the release body. /// /// Using this flag without an argument will open your editor. body: Option<Option<String>>, #[clap(long, short)] draft: Option<bool>, #[clap(long, short)] prerelease: Option<bool>, }, Delete { name: String, #[clap(long, short = 't')] by_tag: bool, }, List { #[clap(long, short = 'p')] include_prerelease: bool, #[clap(long, short = 'd')] include_draft: bool, }, View { name: String, #[clap(long, short = 't')] by_tag: bool, }, Browse { name: Option<String>, }, #[clap(subcommand)] Asset(AssetCommand), } #[derive(Subcommand, Clone, Debug)] pub enum AssetCommand { Create { release: String, path: std::path::PathBuf, name: Option<String>, }, Delete { release: String, asset: String, }, Download { release: String, asset: String, #[clap(long, short)] output: Option<std::path::PathBuf>, }, } impl ReleaseCommand { pub async fn run(self, keys: &KeyInfo, remote_name: Option<&str>) -> eyre::Result<()> { let repo = RepoInfo::get_current(remote_name, self.repo.as_deref(), self.remote.as_deref())?; let api = keys.get_api(&repo.host_url())?; let repo = repo .name() .ok_or_eyre("couldn't get repo name, try specifying with --repo")?; match self.command { ReleaseSubcommand::Create { name, create_tag, tag, attach, body, branch, draft, prerelease, } => { create_release( &repo, &api, name, create_tag, tag, attach, body, branch, draft, prerelease, ) .await? } ReleaseSubcommand::Edit { name, rename, tag, body, draft, prerelease, } => edit_release(&repo, &api, name, rename, tag, body, draft, prerelease).await?, ReleaseSubcommand::Delete { name, by_tag } => { delete_release(&repo, &api, name, by_tag).await? } ReleaseSubcommand::List { include_prerelease, include_draft, } => list_releases(&repo, &api, include_prerelease, include_draft).await?, ReleaseSubcommand::View { name, by_tag } => { view_release(&repo, &api, name, by_tag).await? } ReleaseSubcommand::Browse { name } => browse_release(&repo, &api, name).await?, ReleaseSubcommand::Asset(subcommand) => match subcommand { AssetCommand::Create { release, path, name, } => create_asset(&repo, &api, release, path, name).await?, AssetCommand::Delete { release, asset } => { delete_asset(&repo, &api, release, asset).await? } AssetCommand::Download { release, asset, output, } => download_asset(&repo, &api, release, asset, output).await?, }, } Ok(()) } } async fn create_release( repo: &RepoName, api: &Forgejo, name: String, create_tag: Option<Option<String>>, tag: Option<String>, attachments: Vec<String>, body: Option<Option<String>>, branch: Option<String>, draft: bool, prerelease: bool, ) -> eyre::Result<()> { let tag_name = match (tag, create_tag) { (None, None) => bail!("must select tag with `--tag` or `--create-tag`"), (Some(tag), None) => tag, (None, Some(tag)) => { let tag = tag.unwrap_or_else(|| name.clone()); let opt = forgejo_api::structs::CreateTagOption { message: None, tag_name: tag.clone(), target: branch, }; api.repo_create_tag(repo.owner(), repo.name(), opt).await?; tag } (Some(_), Some(_)) => { bail!("`--tag` and `--create-tag` are mutually exclusive; please pick just one") } }; let body = match body { Some(Some(body)) => Some(body), Some(None) => { let mut s = String::new(); crate::editor(&mut s, Some("md")).await?; Some(s) } None => None, }; let release_opt = forgejo_api::structs::CreateReleaseOption { body, draft: Some(draft), name: Some(name), prerelease: Some(prerelease), tag_name, target_commitish: None, }; let release = api .repo_create_release(repo.owner(), repo.name(), release_opt) .await?; for attachment in attachments { let (file, asset) = match attachment.split_once(':') { Some((file, asset)) => (std::path::Path::new(file), asset), None => { let file = std::path::Path::new(&attachment); let asset = file .file_name() .ok_or_else(|| eyre!("{attachment} does not have a file name"))? .to_str() .unwrap(); (file, asset) } }; let query = RepoCreateReleaseAttachmentQuery { name: Some(asset.into()), }; let id = release .id .ok_or_else(|| eyre::eyre!("release does not have id"))?; api.repo_create_release_attachment( repo.owner(), repo.name(), id, tokio::fs::read(file).await?, query, ) .await?; } Ok(()) } async fn edit_release( repo: &RepoName, api: &Forgejo, name: String, rename: Option<String>, tag: Option<String>, body: Option<Option<String>>, draft: Option<bool>, prerelease: Option<bool>, ) -> eyre::Result<()> { let release = find_release(repo, api, &name).await?; let body = match body { Some(Some(body)) => Some(body), Some(None) => { let mut s = release .body .clone() .ok_or_else(|| eyre::eyre!("release does not have body"))?; crate::editor(&mut s, Some("md")).await?; Some(s) } None => None, }; let release_edit = forgejo_api::structs::EditReleaseOption { name: rename, tag_name: tag, body, draft, prerelease, target_commitish: None, }; let id = release .id .ok_or_else(|| eyre::eyre!("release does not have id"))?; api.repo_edit_release(repo.owner(), repo.name(), id, release_edit) .await?; Ok(()) } async fn list_releases( repo: &RepoName, api: &Forgejo, prerelease: bool, draft: bool, ) -> eyre::Result<()> { let query = forgejo_api::structs::RepoListReleasesQuery { pre_release: Some(prerelease), draft: Some(draft), page: None, limit: None, }; let releases = api .repo_list_releases(repo.owner(), repo.name(), query) .await?; for release in releases { let name = release .name .as_ref() .ok_or_else(|| eyre::eyre!("release does not have name"))?; let draft = release .draft .as_ref() .ok_or_else(|| eyre::eyre!("release does not have draft"))?; let prerelease = release .prerelease .as_ref() .ok_or_else(|| eyre::eyre!("release does not have prerelease"))?; print!("{}", name); match (draft, prerelease) { (false, false) => (), (true, false) => print!(" (draft)"), (false, true) => print!(" (prerelease)"), (true, true) => print!(" (draft, prerelease)"), } println!(); } Ok(()) } async fn view_release( repo: &RepoName, api: &Forgejo, name: String, by_tag: bool, ) -> eyre::Result<()> { let release = if by_tag { api.repo_get_release_by_tag(repo.owner(), repo.name(), &name) .await? } else { find_release(repo, api, &name).await? }; let name = release .name .as_ref() .ok_or_else(|| eyre::eyre!("release does not have name"))?; let author = release .author .as_ref() .ok_or_else(|| eyre::eyre!("release does not have author"))?; let login = author .login .as_ref() .ok_or_else(|| eyre::eyre!("autho does not have login"))?; let created_at = release .created_at .ok_or_else(|| eyre::eyre!("release does not have created_at"))?; println!("{}", name); print!("By {} on ", login); created_at.format_into( &mut std::io::stdout(), &time::format_description::well_known::Rfc2822, )?; println!(); let SpecialRender { bullet, body_prefix, dark_grey, reset, .. } = crate::special_render(); let body = release .body .as_ref() .ok_or_else(|| eyre::eyre!("release does not have body"))?; if !body.is_empty() { println!(); for line in body.lines() { println!("{dark_grey}{body_prefix}{reset} {line}"); } println!(); } let assets = release .assets .as_ref() .ok_or_else(|| eyre::eyre!("release does not have assets"))?; if !assets.is_empty() { println!("{} assets", assets.len() + 2); for asset in assets { let name = asset .name .as_ref() .ok_or_else(|| eyre::eyre!("asset does not have name"))?; println!("{bullet} {}", name); } println!("{bullet} source.zip"); println!("{bullet} source.tar.gz"); } Ok(()) } async fn browse_release(repo: &RepoName, api: &Forgejo, name: Option<String>) -> eyre::Result<()> { match name { Some(name) => { let release = find_release(repo, api, &name).await?; let html_url = release .html_url .as_ref() .ok_or_else(|| eyre::eyre!("release does not have html_url"))?; open::that(html_url.as_str())?; } None => { let repo_data = api.repo_get(repo.owner(), repo.name()).await?; let mut html_url = repo_data .html_url .clone() .ok_or_else(|| eyre::eyre!("repository does not have html_url"))?; html_url.path_segments_mut().unwrap().push("releases"); open::that(html_url.as_str())?; } } Ok(()) } async fn create_asset( repo: &RepoName, api: &Forgejo, release: String, file: std::path::PathBuf, asset: Option<String>, ) -> eyre::Result<()> { let (file, asset) = match asset { Some(ref asset) => (&*file, &**asset), None => { let asset = file .file_name() .ok_or_else(|| eyre!("{} does not have a file name", file.display()))? .to_str() .unwrap(); (&*file, asset) } }; let id = find_release(repo, api, &release) .await? .id .ok_or_else(|| eyre::eyre!("release does not have id"))?; let query = RepoCreateReleaseAttachmentQuery { name: Some(asset.to_owned()), }; api.repo_create_release_attachment( repo.owner(), repo.name(), id, tokio::fs::read(file).await?, query, ) .await?; Ok(()) } async fn delete_asset( repo: &RepoName, api: &Forgejo, release: String, asset: String, ) -> eyre::Result<()> { let release = find_release(repo, api, &release).await?; let assets = release .assets .as_ref() .ok_or_else(|| eyre::eyre!("release does not have assets"))?; let asset = assets .iter() .find(|a| a.name.as_ref() == Some(&asset)) .ok_or_else(|| eyre!("asset not found"))?; let release_id = release .id .ok_or_else(|| eyre::eyre!("release does not have id"))?; let asset_id = asset .id .ok_or_else(|| eyre::eyre!("asset does not have id"))?; api.repo_delete_release_attachment(repo.owner(), repo.name(), release_id, asset_id) .await?; Ok(()) } async fn download_asset( repo: &RepoName, api: &Forgejo, release: String, asset: String, output: Option<std::path::PathBuf>, ) -> eyre::Result<()> { let release = find_release(repo, api, &release).await?; let file = match &*asset { "source.zip" => { let tag_name = release .tag_name .as_ref() .ok_or_else(|| eyre::eyre!("release does not have tag_name"))?; api.repo_get_archive(repo.owner(), repo.name(), &format!("{}.zip", tag_name)) .await? } "source.tar.gz" => { let tag_name = release .tag_name .as_ref() .ok_or_else(|| eyre::eyre!("release does not have tag_name"))?; api.repo_get_archive(repo.owner(), repo.name(), &format!("{}.tar.gz", tag_name)) .await? } name => { let assets = release .assets .as_ref() .ok_or_else(|| eyre::eyre!("release does not have assets"))?; let asset = assets .iter() .find(|a| a.name.as_deref() == Some(name)) .ok_or_else(|| eyre!("asset not found"))?; let release_id = release .id .ok_or_else(|| eyre::eyre!("release does not have id"))?; let asset_id = asset .id .ok_or_else(|| eyre::eyre!("asset does not have id"))?; api.download_release_attachment(repo.owner(), repo.name(), release_id, asset_id) .await? .to_vec() } }; let output = output .as_deref() .unwrap_or_else(|| std::path::Path::new(&asset)); tokio::fs::OpenOptions::new() .create_new(true) .write(true) .open(output) .await? .write_all(file.as_ref()) .await?; Ok(()) } async fn find_release( repo: &RepoName, api: &Forgejo, name: &str, ) -> eyre::Result<forgejo_api::structs::Release> { let query = RepoListReleasesQuery { draft: None, pre_release: None, page: None, limit: None, }; let mut releases = api .repo_list_releases(repo.owner(), repo.name(), query) .await?; let idx = releases .iter() .position(|r| r.name.as_deref() == Some(name)) .ok_or_else(|| eyre!("release not found"))?; Ok(releases.swap_remove(idx)) } async fn delete_release( repo: &RepoName, api: &Forgejo, name: String, by_tag: bool, ) -> eyre::Result<()> { if by_tag { api.repo_delete_release_by_tag(repo.owner(), repo.name(), &name) .await?; } else { let id = find_release(repo, api, &name) .await? .id .ok_or_else(|| eyre::eyre!("release does not have id"))?; api.repo_delete_release(repo.owner(), repo.name(), id) .await?; } Ok(()) } 07070100000010000081A4000000000000000000000001663A7FD9000045AF000000000000000000000000000000000000002C00000000forgejo-cli-0.0.4+git13.0a30d14/src/repo.rsuse clap::Subcommand; use eyre::{eyre, OptionExt}; use forgejo_api::structs::CreateRepoOption; use url::Url; use crate::SpecialRender; pub struct RepoInfo { url: Url, name: Option<RepoName>, } impl RepoInfo { pub fn get_current( host: Option<&str>, repo: Option<&str>, remote: Option<&str>, ) -> eyre::Result<Self> { // l = domain/owner/name // s = owner/name // x = is present // i = found locally by git // // | repo | host | remote | ans-host | ans-repo | // |------|------|--------|----------|----------| // | l | x | x | repo | repo | // | l | x | i | repo | repo | // | l | x | | repo | repo | // | l | | x | repo | repo | // | l | | i | repo | repo | // | l | | | repo | repo | // | s | x | x | host | repo | // | s | x | i | host | repo | // | s | x | | host | repo | // | s | | x | remote | repo | // | s | | i | remote | repo | // | s | | | err | repo | // | | x | x | remote | remote | // | | x | i | host | ?remote | // | | x | | host | none | // | | | x | remote | remote | // | | | i | remote | remote | // | | | | err | remote | // // | repo | host | remote | ans-host | ans-repo | // |------|------|--------|----------|----------| // | l | x | x | repo | repo | // | l | x | | repo | repo | // | l | | x | repo | repo | // | l | | | repo | repo | // | s | x | x | host | repo | // | s | x | | host | repo | // | s | | x | remote | repo | // | s | | | err | repo | // | | x | x | remote | remote | // | | x | | remote | remote | // | | | x | remote | remote | // | | | | err | remote | // let repo_name; // // let repo_url; // let remote; // let host; // // let url = if repo_url { repo_url } // else if repo_name { host.or(remote) } // else { remote.or_host() } // // let name = repo_name.or(remote) let mut repo_url: Option<Url> = None; let mut repo_name: Option<RepoName> = None; if let Some(repo) = repo { let (head, name) = repo .rsplit_once("/") .ok_or_eyre("repo name must contain owner and name")?; let name = name.strip_suffix(".git").unwrap_or(name); match head.rsplit_once("/") { Some((url, owner)) => { if let Ok(url) = Url::parse(url) { repo_url = Some(url); } else if let Ok(url) = Url::parse(&format!("https://{url}/")) { repo_url = Some(url); } repo_name = Some(RepoName { owner: owner.to_owned(), name: name.to_owned(), }); } None => { repo_name = Some(RepoName { owner: head.to_owned(), name: name.to_owned(), }); } } } let repo_url = repo_url; let repo_name = repo_name; let host_url = host.and_then(|host| { Url::parse(host) .ok() .or_else(|| Url::parse(&format!("https://{host}/")).ok()) }); let (remote_url, remote_repo_name) = { let mut out = (None, None); if let Ok(local_repo) = git2::Repository::open(".") { // help to escape scopes let tmp; let mut tmp2; let mut name = remote; // if there's only one remote, use that if name.is_none() { let all_remotes = local_repo.remotes()?; if all_remotes.len() == 1 { if let Some(remote_name) = all_remotes.get(0) { tmp2 = Some(remote_name.to_owned()); name = tmp2.as_deref(); } } } // if the current branch is tracking a remote branch, use that remote if name.is_none() { let head = local_repo.head()?; let branch_name = head.name().ok_or_else(|| eyre!("branch name not UTF-8"))?; tmp = local_repo.branch_upstream_remote(branch_name).ok(); name = tmp .as_ref() .map(|remote| { remote .as_str() .ok_or_else(|| eyre!("remote name not UTF-8")) }) .transpose()?; } // if there's a remote whose host url matches the one // specified with `--host`, use that // // This is different than using `--host` itself, since this // will include the repo name, which `--host` can't do. if name.is_none() { if let Some(host_url) = &host_url { let all_remotes = local_repo.remotes()?; for remote_name in all_remotes.iter() { let Some(remote_name) = remote_name else { continue; }; let remote = local_repo.find_remote(remote_name)?; if let Some(url) = remote.url() { let (url, _) = url_strip_repo_name(Url::parse(url)?)?; if url.host_str() == host_url.host_str() && url.path() == host_url.path() { tmp2 = Some(remote_name.to_owned()); name = tmp2.as_deref(); } } } } } if let Some(name) = name { if let Ok(remote) = local_repo.find_remote(name) { let url_s = std::str::from_utf8(remote.url_bytes())?; let url = Url::parse(url_s)?; let (url, name) = url_strip_repo_name(url)?; out = (Some(url), Some(name)) } } } else { eyre::ensure!(remote.is_none(), "remote specified but no git repo found"); } out }; let (url, name) = if repo_url.is_some() { (repo_url, repo_name) } else if repo_name.is_some() { (host_url.or(remote_url), repo_name) } else { if remote.is_some() { (remote_url, remote_repo_name) } else if host_url.is_none() || remote_url == host_url { (remote_url, remote_repo_name) } else { (host_url, None) } }; let info = match (url, name) { (Some(url), name) => RepoInfo { url, name }, (None, Some(_)) => eyre::bail!("cannot find repo, no host specified"), (None, None) => eyre::bail!("no repo info specified"), }; Ok(info) } pub fn name(&self) -> Option<&RepoName> { self.name.as_ref() } pub fn host_url(&self) -> &Url { &self.url } } fn url_strip_repo_name(mut url: Url) -> eyre::Result<(Url, RepoName)> { let mut iter = url .path_segments() .ok_or_eyre("repo url cannot be a base")? .rev(); let name = iter.next().ok_or_eyre("repo url too short")?; let name = name.strip_suffix(".git").unwrap_or(name).to_owned(); let owner = iter.next().ok_or_eyre("repo url too short")?.to_owned(); // Remove the username and repo name from the url url.path_segments_mut() .map_err(|_| eyre!("repo url cannot be a base"))? .pop() .pop(); Ok((url, RepoName { owner, name })) } #[derive(Debug)] pub struct RepoName { owner: String, name: String, } impl RepoName { pub fn owner(&self) -> &str { &self.owner } pub fn name(&self) -> &str { &self.name } } #[derive(Subcommand, Clone, Debug)] pub enum RepoCommand { Create { repo: String, // flags #[clap(long, short)] description: Option<String>, #[clap(long, short = 'P')] private: bool, /// Sets the new repo to be the `origin` remote of the current local repo. #[clap(long, short)] set_upstream: Option<String>, /// Pushes the current branch to the default branch on the new repo. /// Implies `--set-upstream=origin` (setting upstream manual overrides this) #[clap(long, short)] push: bool, }, Info { name: Option<String>, #[clap(long, short = 'R')] remote: Option<String>, }, Browse { name: Option<String>, #[clap(long, short = 'R')] remote: Option<String>, }, } impl RepoCommand { pub async fn run(self, keys: &crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> { match self { RepoCommand::Create { repo, description, private, set_upstream, push, } => { let host = RepoInfo::get_current(host_name, None, None)?; let api = keys.get_api(host.host_url())?; let repo_spec = CreateRepoOption { auto_init: Some(false), default_branch: Some("main".into()), description, gitignores: None, issue_labels: None, license: None, name: repo.clone(), object_format_name: None, private: Some(private), readme: Some(String::new()), template: Some(false), trust_model: Some(forgejo_api::structs::CreateRepoOptionTrustModel::Default), }; let new_repo = api.create_current_user_repo(repo_spec).await?; let full_name = new_repo .full_name .as_ref() .ok_or_else(|| eyre::eyre!("new_repo does not have full_name"))?; eprintln!("created new repo at {}", host.host_url().join(&full_name)?); if set_upstream.is_some() || push { let repo = git2::Repository::open(".")?; let upstream = set_upstream.as_deref().unwrap_or("origin"); let clone_url = new_repo .clone_url .as_ref() .ok_or_else(|| eyre::eyre!("new_repo does not have clone_url"))?; let mut remote = repo.remote(upstream, clone_url.as_str())?; if push { let head = repo.head()?; if !head.is_branch() { eyre::bail!("HEAD is not on a branch; cannot push to remote"); } let branch_shorthand = head .shorthand() .ok_or_else(|| eyre!("branch name invalid utf-8"))? .to_owned(); let branch_name = std::str::from_utf8(head.name_bytes())?.to_owned(); let mut current_branch = git2::Branch::wrap(head); current_branch .set_upstream(Some(&dbg!(format!("{upstream}/{branch_shorthand}"))))?; let auth = auth_git2::GitAuthenticator::new(); auth.push(&repo, &mut remote, &[&branch_name])?; } } } RepoCommand::Info { name, remote } => { let repo = RepoInfo::get_current(host_name, name.as_deref(), remote.as_deref())?; let api = keys.get_api(&repo.host_url())?; let repo = repo .name() .ok_or_eyre("couldn't get repo name, please specify")?; let repo = api.repo_get(repo.owner(), repo.name()).await?; let SpecialRender { dash, body_prefix, dark_grey, reset, .. } = crate::special_render(); println!("{}", repo.full_name.ok_or_eyre("no full name")?); if let Some(parent) = &repo.parent { println!( "Fork of {}", parent.full_name.as_ref().ok_or_eyre("no full name")? ); } if repo.mirror == Some(true) { if let Some(original) = &repo.original_url { println!("Mirror of {original}") } } let desc = repo.description.as_deref().unwrap_or_default(); if !desc.is_empty() { if desc.lines().count() > 1 { println!(); } for line in desc.lines() { println!("{dark_grey}{body_prefix}{reset} {line}"); } } println!(); let lang = repo.language.as_deref().unwrap_or_default(); if !lang.is_empty() { println!("Primary language is {lang}"); } let stars = repo.stars_count.unwrap_or_default(); if stars == 1 { print!("{stars} star {dash} "); } else { print!("{stars} stars {dash} "); } let watchers = repo.watchers_count.unwrap_or_default(); print!("{watchers} watching {dash} "); let forks = repo.forks_count.unwrap_or_default(); if forks == 1 { print!("{forks} fork"); } else { print!("{forks} forks"); } println!(); let mut first = true; if repo.has_issues.unwrap_or_default() && repo.external_tracker.is_none() { let issues = repo.open_issues_count.unwrap_or_default(); if issues == 1 { print!("{issues} issue"); } else { print!("{issues} issues"); } first = false; } if repo.has_pull_requests.unwrap_or_default() { if !first { print!(" {dash} "); } let pulls = repo.open_pr_counter.unwrap_or_default(); if pulls == 1 { print!("{pulls} PR"); } else { print!("{pulls} PRs"); } first = false; } if repo.has_releases.unwrap_or_default() { if !first { print!(" {dash} "); } let releases = repo.release_counter.unwrap_or_default(); if releases == 1 { print!("{releases} release"); } else { print!("{releases} releases"); } first = false; } if !first { println!(); } if let Some(external_tracker) = &repo.external_tracker { if let Some(tracker_url) = &external_tracker.external_tracker_url { println!("Issue tracker is at {tracker_url}"); } } if let Some(html_url) = &repo.html_url { println!(); println!("View online at {html_url}"); } } RepoCommand::Browse { name, remote } => { let repo = RepoInfo::get_current(host_name, name.as_deref(), remote.as_deref())?; let mut url = repo.host_url().clone(); let repo = repo .name() .ok_or_eyre("couldn't get repo name, please specify")?; url.path_segments_mut() .map_err(|_| eyre!("url invalid"))? .extend([repo.owner(), repo.name()]); open::that(url.as_str())?; } }; Ok(()) } } 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!248 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor