tealdeer-1.7.0/.cargo_vcs_info.json0000644000000001360000000000100126260ustar { "git": { "sha1": "d44613cf59445b910a75b7df489466f1948b5d44" }, "path_in_vcs": "" }tealdeer-1.7.0/Cargo.lock0000644000001473270000000000100106170ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "anyhow" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "app_dirs2" version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7e7b35733e3a8c1ccb90385088dd5b6eaa61325cb4d1ad56e683b5224ff352e" dependencies = [ "jni", "ndk-context", "winapi", "xdg", ] [[package]] name = "arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" dependencies = [ "derive_arbitrary", ] [[package]] name = "assert_cmd" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ "anstyle", "bstr", "doc-comment", "libc", "predicates", "predicates-core", "predicates-tree", "wait-timeout", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bstr" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "shlex", ] [[package]] name = "cesu8" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[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.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", "clap_lex", "terminal_size", ] [[package]] name = "clap_derive" version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", ] [[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.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "derive_arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "env_filter" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", "env_filter", "humantime", "log", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", "winapi", ] [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "escargot" version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c000f23e9d459aef148b7267e02b03b94a0aaacf4ec64c65612f67e02f525fb6" dependencies = [ "log", "once_cell", "serde", "serde_json", ] [[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "filetime" version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", "libredox", "windows-sys 0.59.0", ] [[package]] name = "flate2" version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "float-cmp" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" dependencies = [ "num-traits", ] [[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 = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[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-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[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-core", "futures-io", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[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 = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "httparse", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", "webpki-roots", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", ] [[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 = "indexmap" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "ipnet" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", "cfg-if", "combine", "jni-sys", "log", "thiserror", "walkdir", "windows-sys 0.45.0", ] [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags", "libc", "redox_syscall", ] [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lockfree-object-pool" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", ] [[package]] name = "native-tls" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "ndk-context" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" dependencies = [ "portable-atomic", ] [[package]] name = "openssl" version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags", "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.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "pager" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2599211a5c97fbbb1061d3dc751fa15f404927e4846e07c643287d6d1f462880" dependencies = [ "errno 0.2.8", "libc", ] [[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.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "predicates" version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", "float-cmp", "normalize-line-endings", "predicates-core", "regex", ] [[package]] name = "predicates-core" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quinn" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", "socket2", "thiserror", "tokio", "tracing", ] [[package]] name = "quinn-proto" version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", "rand", "ring", "rustc-hash", "rustls", "slab", "thiserror", "tinyvec", "tracing", ] [[package]] name = "quinn-udp" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ "libc", "once_cell", "socket2", "tracing", "windows-sys 0.59.0", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", "rustls-native-certs", "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-native-tls", "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "webpki-roots", "windows-registry", ] [[package]] name = "ring" version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom", "libc", "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustix" version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags", "errno 0.3.9", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls" version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ "rustls-pki-types", ] [[package]] name = "rustls-pki-types" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "schannel" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "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 = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[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 = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" dependencies = [ "futures-core", ] [[package]] name = "tealdeer" version = "1.7.0" dependencies = [ "anyhow", "app_dirs2", "assert_cmd", "clap", "env_logger", "escargot", "filetime", "log", "pager", "predicates", "reqwest", "serde", "serde_derive", "tempfile", "toml", "walkdir", "yansi", "zip", ] [[package]] name = "tempfile" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "terminal_size" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ "rustix", "windows-sys 0.59.0", ] [[package]] name = "termtree" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinyvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 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.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", "socket2", "windows-sys 0.52.0", ] [[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-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", "tokio", ] [[package]] name = "toml" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[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 = "unicode-bidi" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[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.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki-roots" version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] [[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-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[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-registry" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", "windows-targets 0.52.6", ] [[package]] name = "windows-result" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[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.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] [[package]] name = "xdg" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zip" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" dependencies = [ "arbitrary", "crc32fast", "crossbeam-utils", "displaydoc", "flate2", "indexmap", "memchr", "thiserror", "zopfli", ] [[package]] name = "zopfli" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" dependencies = [ "bumpalo", "crc32fast", "lockfree-object-pool", "log", "once_cell", "simd-adler32", ] tealdeer-1.7.0/Cargo.toml0000644000000051500000000000100106250ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.75" name = "tealdeer" version = "1.7.0" authors = [ "Danilo Bargen ", "Niklas Mohrin ", ] build = false include = [ "/src/**/*", "/tests/**/*", "/Cargo.toml", "/README.md", "/LICENSE-*", "/screenshot.png", "completion/*", ] autobins = false autoexamples = false autotests = false autobenches = false description = "Fetch and show tldr help pages for many CLI commands. Full featured offline client with caching support." homepage = "https://github.com/tealdeer-rs/tealdeer/" documentation = "https://tealdeer-rs.github.io/tealdeer/" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/tealdeer-rs/tealdeer/" [profile.release] opt-level = 3 lto = true codegen-units = 1 strip = true [[bin]] name = "tldr" path = "src/main.rs" [[test]] name = "lib" path = "tests/lib.rs" [dependencies.anyhow] version = "1" [dependencies.app_dirs] version = "2" package = "app_dirs2" [dependencies.clap] version = "4" features = [ "std", "derive", "help", "usage", "cargo", "error-context", "color", "wrap_help", ] default-features = false [dependencies.env_logger] version = "0.11" optional = true [dependencies.log] version = "0.4" [dependencies.reqwest] version = "0.12.5" features = ["blocking"] default-features = false [dependencies.serde] version = "1.0.21" [dependencies.serde_derive] version = "1.0.21" [dependencies.toml] version = "0.8.19" [dependencies.walkdir] version = "2.0.1" [dependencies.yansi] version = "0.5" [dependencies.zip] version = "2.1.6" features = ["deflate"] default-features = false [dev-dependencies.assert_cmd] version = "2.0.1" [dev-dependencies.escargot] version = "0.5" [dev-dependencies.filetime] version = "0.2.10" [dev-dependencies.predicates] version = "3.1.2" [dev-dependencies.tempfile] version = "3.1.0" [features] default = ["native-roots"] logging = ["env_logger"] native-roots = ["reqwest/rustls-tls-native-roots"] native-tls = ["reqwest/native-tls"] webpki-roots = ["reqwest/rustls-tls-webpki-roots"] [target."cfg(not(windows))".dependencies.pager] version = "0.16" tealdeer-1.7.0/Cargo.toml.orig000064400000000000000000000037421046102023000143130ustar 00000000000000[package] authors = [ "Danilo Bargen ", "Niklas Mohrin ", ] description = "Fetch and show tldr help pages for many CLI commands. Full featured offline client with caching support." homepage = "https://github.com/tealdeer-rs/tealdeer/" license = "MIT OR Apache-2.0" name = "tealdeer" readme = "README.md" repository = "https://github.com/tealdeer-rs/tealdeer/" documentation = "https://tealdeer-rs.github.io/tealdeer/" version = "1.7.0" include = ["/src/**/*", "/tests/**/*", "/Cargo.toml", "/README.md", "/LICENSE-*", "/screenshot.png", "completion/*"] rust-version = "1.75" edition = "2021" [[bin]] name = "tldr" path = "src/main.rs" [dependencies] anyhow = "1" app_dirs = { version = "2", package = "app_dirs2" } clap = { version = "4", features = ["std", "derive", "help", "usage", "cargo", "error-context", "color", "wrap_help"], default-features = false } env_logger = { version = "0.11", optional = true } log = "0.4" reqwest = { version = "0.12.5", features = ["blocking"], default-features = false } serde = "1.0.21" serde_derive = "1.0.21" toml = "0.8.19" walkdir = "2.0.1" yansi = "0.5" zip = { version = "2.1.6", default-features = false, features = ["deflate"] } [target.'cfg(not(windows))'.dependencies] pager = "0.16" [dev-dependencies] assert_cmd = "2.0.1" escargot = "0.5" predicates = "3.1.2" tempfile = "3.1.0" filetime = "0.2.10" [features] default = ["native-roots"] logging = ["env_logger"] # Reqwest (the HTTP client library) can handle TLS connections in three # different modes: # # - Rustls with native roots # - Rustls with WebPK roots # - Native TLS (SChannel on Windows, Secure Transport on macOS and OpenSSL otherwise) # # Exactly one of the three variants must be selected. By default, Rustls with # native roots is enabled. native-roots = ["reqwest/rustls-tls-native-roots"] webpki-roots = ["reqwest/rustls-tls-webpki-roots"] native-tls = ["reqwest/native-tls"] [profile.release] strip = true opt-level = 3 lto = true codegen-units = 1 tealdeer-1.7.0/LICENSE-APACHE000064400000000000000000000227731046102023000133550ustar 00000000000000 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 tealdeer-1.7.0/LICENSE-MIT000064400000000000000000000020671046102023000130570ustar 00000000000000Copyright (C) 2015-2021 Danilo Bargen and contributors 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. tealdeer-1.7.0/README.md000064400000000000000000000125671046102023000127100ustar 00000000000000# tealdeer ![teal deer](docs/src/deer.png) |Crate|CI (Linux/macOS/Windows)| |:---:|:---:| |[![Crates.io][crates-io-badge]][crates-io]|[![GitHub CI][github-actions-badge]][github-actions]| A very fast implementation of [tldr](https://github.com/tldr-pages/tldr) in Rust: Simplified, example based and community-driven man pages. Screenshot of tldr command If you pronounce "tldr" in English, it sounds somewhat like "tealdeer". Hence the project name :) In case you're in a hurry and just want to quickly try tealdeer, you can find static binaries on the [GitHub releases page](https://github.com/tealdeer-rs/tealdeer/releases/)! ## Docs (Installing, Usage, Configuration) User documentation is available at ! The docs are generated using [mdbook](https://rust-lang.github.io/mdBook/index.html). They can be edited through the markdown files in the `docs/src/` directory. ## Goals High level project goals: - [x] Download and cache pages - [x] Don't require a network connection for anything besides updating the cache - [x] Command line interface similar or equivalent to the [NodeJS client][node-gh] - [x] Comply with the [tldr client specification][client-spec] - [x] Advanced highlighting and configuration - [x] Be fast A tool like `tldr` should be as frictionless as possible to use and show the output as fast as possible. We think that `tealdeer` reaches these goals. We put together a (more or less) reproducible benchmark that compiles a handful of clients from source and measures the execution times on a cold disk cache. The benchmarking is run in a Docker container using sharkdp's [`hyperfine`][hyperfine-gh] ([Dockerfile][benchmark-dockerfile]). | Client (50 runs, 17.10.2021) | Programming Language | Mean in ms | Deviation in ms | Comments | | :---: | :---: | :---: | :---: | :---: | | [`outfieldr`][outfieldr-gh] | Zig | 9.1 | 0.5 | no user configuration | | `tealdeer` | Rust | 13.2 | 0.5 | | | [`fast-tldr`][fast-tldr-gh] | Haskell | 17.0 | 0.6 | no example highlighting | | [`tldr-hs`][hs-gh] | Haskell | 25.1 | 0.5 | no example highlighting | | [`tldr-bash`][bash-gh] | Bash | 30.0 | 0.8 | | | [`tldr-c`][c-gh] | C | 38.4 | 1.0 | | | [`tldr-python-client`][python-gh] | Python | 87.0 | 2.4 | | | [`tldr-node-client`][node-gh] | JavaScript / NodeJS | 407.1 | 12.9 | | As you can see, `tealdeer` is one of the fastest of the tested clients. However, we strive for useful features and code quality over raw performance, even if that means that we don't come out on top in this friendly competition. That said, we are still optimizing the code, for example when the `outfieldr` developers [suggested to switch][outfieldr-comment-tls] to a native TLS implementation instead of the native libraries. ## Development Creating a debug build with logging enabled: $ cargo build --features logging Release build without logging: $ cargo build --release To enable the log output, set the `RUST_LOG` env variable: $ export RUST_LOG=tldr=debug To run tests: $ cargo test To run lints: $ rustup component add clippy $ cargo clean && cargo clippy ## MSRV (Minimally Supported Rust Version) When publishing a tealdeer release, the Rust version required to build it should be stable for at least a month. ## License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution 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. Thanks to @severen for coming up with the name "tealdeer"! [node-gh]: https://github.com/tldr-pages/tldr-node-client [c-gh]: https://github.com/tldr-pages/tldr-c-client [hs-gh]: https://github.com/psibi/tldr-hs [fast-tldr-gh]: https://github.com/gutjuri/fast-tldr [bash-gh]: https://4e4.win/tldr [outfieldr-gh]: https://gitlab.com/ve-nt/outfieldr [python-gh]: https://github.com/tldr-pages/tldr-python-client [benchmark-dockerfile]: https://github.com/tealdeer-rs/tealdeer/blob/main/benchmarks/Dockerfile [client-spec]: https://github.com/tldr-pages/tldr/blob/main/CLIENT-SPECIFICATION.md [hyperfine-gh]: https://github.com/sharkdp/hyperfine [outfieldr-comment-tls]: https://github.com/tealdeer-rs/tealdeer/issues/129#issuecomment-833596765 [github-actions]: https://github.com/tealdeer-rs/tealdeer/actions?query=branch%3Amain [github-actions-badge]: https://github.com/tealdeer-rs/tealdeer/actions/workflows/ci.yml/badge.svg?branch=main [crates-io]: https://crates.io/crates/tealdeer [crates-io-badge]: https://img.shields.io/crates/v/tealdeer.svg tealdeer-1.7.0/completion/bash_tealdeer000064400000000000000000000014401046102023000162730ustar 00000000000000# tealdeer bash completion _tealdeer() { local cur prev words cword _init_completion || return case $prev in -h|--help|-v|--version|-l|--list|-u|--update|--no-auto-update|-c|--clear-cache|--pager|-r|--raw|--show-paths|--seed-config|-q|--quiet) return ;; -f|--render) _filedir return ;; -p|--platform) COMPREPLY=( $(compgen -W 'linux macos sunos windows android freebsd netbsd openbsd' -- "${cur}") ) return ;; --color) COMPREPLY=( $(compgen -W 'always auto never' -- "${cur}") ) return ;; esac if [[ $cur == -* ]]; then COMPREPLY=( $( compgen -W '$( _parse_help "$1" )' -- "$cur" ) ) return fi if tldrlist=$(tldr -l 2>/dev/null); then COMPREPLY=( $(compgen -W '$( echo "$tldrlist" | tr -d , )' -- "${cur}") ) fi } complete -F _tealdeer tldr tealdeer-1.7.0/completion/fish_tealdeer000064400000000000000000000030421046102023000163070ustar 00000000000000# # Completions for the tealdeer implementation of tldr # https://github.com/tealdeer-rs/tealdeer/ # complete -c tldr -s h -l help -d 'Print the help message.' -f complete -c tldr -s v -l version -d 'Show version information.' -f complete -c tldr -s l -l list -d 'List all commands in the cache.' -f complete -c tldr -s f -l render -d 'Render a specific markdown file.' -r complete -c tldr -s p -l platform -d 'Override the operating system.' -xa 'linux macos sunos windows android freebsd netbsd openbsd' complete -c tldr -s L -l language -d 'Override the language' -x complete -c tldr -s u -l update -d 'Update the local cache.' -f complete -c tldr -l no-auto-update -d 'If auto update is configured, disable it for this run.' -f complete -c tldr -s c -l clear-cache -d 'Clear the local cache.' -f complete -c tldr -l pager -d 'Use a pager to page output.' -f complete -c tldr -s r -l raw -d 'Display the raw markdown instead of rendering it.' -f complete -c tldr -s q -l quiet -d 'Suppress informational messages.' -f complete -c tldr -l show-paths -d 'Show file and directory paths used by tealdeer.' -f complete -c tldr -l seed-config -d 'Create a basic config.' -f complete -c tldr -l color -d 'Controls when to use color.' -xa 'always auto never' function __tealdeer_entries if set entries (tldr --list 2>/dev/null) string replace -a -i -r "\,\s" "\n" $entries end end complete -f -c tldr -a '(__tealdeer_entries)' tealdeer-1.7.0/completion/zsh_tealdeer000064400000000000000000000031731046102023000161670ustar 00000000000000#compdef tldr _applications() { local -a commands if commands=(${(uonzf)"$(tldr --list 2>/dev/null)"//:/\\:}); then _describe -t commands 'command' commands fi } _tealdeer() { local I="-h --help -v --version" integer ret=1 local -a args args+=( "($I -l --list)"{-l,--list}"[List all commands in the cache]" "($I -f --render)"{-f,--render}"[Render a specific markdown file]:file:_files" "($I -p --platform)"{-p,--platform}'[Override the operating system]:platform:(( linux macos sunos windows android freebsd netbsd openbsd ))' "($I -L --language)"{-L,--language}"[Override the language settings]:lang" "($I -u --update)"{-u,--update}"[Update the local cache]" "($I)--no-auto-update[If auto update is configured, disable it for this run]" "($I -c --clear-cache)"{-c,--clear-cache}"[Clear the local cache]" "($I)--pager[Use a pager to page output]" "($I -r --raw)"{-r,--raw}"[Display the raw markdown instead of rendering it]" "($I -q --quiet)"{-q,--quiet}"[Suppress informational messages]" "($I)--show-paths[Show file and directory paths used by tealdeer]" "($I)--seed-config[Create a basic config]" "($I)--color[Controls when to use color]:when:(( always auto never ))" '(- *)'{-h,--help}'[Display help]' '(- *)'{-v,--version}'[Show version information]' '1: :_applications' ) _arguments $args[@] && ret=0 return ret } _tealdeer tealdeer-1.7.0/src/cache.rs000064400000000000000000000414311046102023000136210ustar 00000000000000use std::{ env, ffi::OsStr, fs::{self, File}, io::{BufReader, Cursor, Read}, path::{Path, PathBuf}, time::{Duration, SystemTime}, }; use anyhow::{ensure, Context, Result}; use log::debug; use reqwest::{blocking::Client, Proxy}; use walkdir::{DirEntry, WalkDir}; use zip::ZipArchive; use crate::{types::PlatformType, utils::print_warning}; pub static TLDR_PAGES_DIR: &str = "tldr-pages"; static TLDR_OLD_PAGES_DIR: &str = "tldr-master"; #[derive(Debug)] pub struct Cache { cache_dir: PathBuf, enable_styles: bool, } #[derive(Debug)] pub struct PageLookupResult { pub page_path: PathBuf, pub patch_path: Option, } impl PageLookupResult { pub fn with_page(page_path: PathBuf) -> Self { Self { page_path, patch_path: None, } } pub fn with_optional_patch(mut self, patch_path: Option) -> Self { self.patch_path = patch_path; self } /// Create a buffered reader that sequentially reads from the page and the /// patch, as if they were concatenated. /// /// This will return an error if either the page file or the patch file /// cannot be opened. pub fn reader(&self) -> Result>> { // Open page file let page_file = File::open(&self.page_path) .with_context(|| format!("Could not open page file at {}", self.page_path.display()))?; // Open patch file let patch_file_opt = match &self.patch_path { Some(path) => Some( File::open(path) .with_context(|| format!("Could not open patch file at {}", path.display()))?, ), None => None, }; // Create chained reader from file(s) // // Note: It might be worthwhile to create our own struct that accepts // the page and patch files and that will read them sequentially, // because it avoids the boxing below. However, the performance impact // would first need to be shown to be significant using a benchmark. Ok(BufReader::new(if let Some(patch_file) = patch_file_opt { Box::new(page_file.chain(&b"\n"[..]).chain(patch_file)) as Box } else { Box::new(page_file) as Box })) } } pub enum CacheFreshness { /// The cache is still fresh (less than `MAX_CACHE_AGE` old) Fresh, /// The cache is stale and should be updated Stale(Duration), /// The cache is missing Missing, } impl Cache { pub fn new

(cache_dir: P, enable_styles: bool) -> Self where P: Into, { Self { cache_dir: cache_dir.into(), enable_styles, } } pub fn cache_dir(&self) -> &Path { &self.cache_dir } /// Make sure that the cache directory exists and is a directory. /// If necessary, create the directory. fn ensure_cache_dir_exists(&self) -> Result<()> { // Check whether `cache_dir` exists and is a directory let (cache_dir_exists, cache_dir_is_dir) = self .cache_dir .metadata() .map_or((false, false), |md| (true, md.is_dir())); ensure!( !cache_dir_exists || cache_dir_is_dir, "Cache directory path `{}` is not a directory", self.cache_dir.display(), ); if !cache_dir_exists { // If missing, try to create the complete directory path fs::create_dir_all(&self.cache_dir).with_context(|| { format!( "Cache directory path `{}` cannot be created", self.cache_dir.display(), ) })?; eprintln!( "Successfully created cache directory path `{}`.", self.cache_dir.display(), ); } Ok(()) } fn pages_dir(&self) -> PathBuf { self.cache_dir.join(TLDR_PAGES_DIR) } /// Download the archive from the specified URL. fn download(archive_url: &str) -> Result> { let mut builder = Client::builder(); if let Ok(ref host) = env::var("HTTP_PROXY") { if let Ok(proxy) = Proxy::http(host) { builder = builder.proxy(proxy); } } if let Ok(ref host) = env::var("HTTPS_PROXY") { if let Ok(proxy) = Proxy::https(host) { builder = builder.proxy(proxy); } } let client = builder .build() .context("Could not instantiate HTTP client")?; let mut resp = client .get(archive_url) .send()? .error_for_status() .with_context(|| format!("Could not download tldr pages from {archive_url}"))?; let mut buf: Vec = vec![]; let bytes_downloaded = resp.copy_to(&mut buf)?; debug!("{} bytes downloaded", bytes_downloaded); Ok(buf) } /// Update the pages cache from the specified URL. pub fn update(&self, archive_url: &str) -> Result<()> { self.ensure_cache_dir_exists()?; // First, download the compressed data let bytes: Vec = Self::download(archive_url)?; // Decompress the response body into an `Archive` let mut archive = ZipArchive::new(Cursor::new(bytes)) .context("Could not decompress downloaded ZIP archive")?; // Clear cache directory // Note: This is not the best solution. Ideally we would download the // archive to a temporary directory and then swap the two directories. // But renaming a directory doesn't work across filesystems and Rust // does not yet offer a recursive directory copying function. So for // now, we'll use this approach. self.clear() .context("Could not clear the cache directory")?; // Extract archive into pages dir archive .extract(self.pages_dir()) .context("Could not unpack compressed data")?; Ok(()) } /// Return the duration since the cache directory was last modified. pub fn last_update(&self) -> Option { if let Ok(metadata) = fs::metadata(self.pages_dir()) { if let Ok(mtime) = metadata.modified() { let now = SystemTime::now(); return now.duration_since(mtime).ok(); }; }; None } /// Return the freshness of the cache (fresh, stale or missing). pub fn freshness(&self) -> CacheFreshness { match self.last_update() { Some(ago) if ago > crate::config::MAX_CACHE_AGE => CacheFreshness::Stale(ago), Some(_) => CacheFreshness::Fresh, None => CacheFreshness::Missing, } } /// Return the platform directory. fn get_platform_dir(platform: PlatformType) -> &'static str { match platform { PlatformType::Linux => "linux", PlatformType::OsX => "osx", PlatformType::SunOs => "sunos", PlatformType::Windows => "windows", PlatformType::Android => "android", PlatformType::FreeBsd => "freebsd", PlatformType::NetBsd => "netbsd", PlatformType::OpenBsd => "openbsd", } } /// Check for pages for a given platform in one of the given languages. fn find_page_for_platform( page_name: &str, pages_dir: &Path, platform: &str, language_dirs: &[String], ) -> Option { language_dirs .iter() .map(|lang_dir| pages_dir.join(lang_dir).join(platform).join(page_name)) .find(|path| path.exists() && path.is_file()) } /// Look up custom patch (.patch.md). If it exists, store it in a variable. fn find_patch(patch_name: &str, custom_pages_dir: Option<&Path>) -> Option { custom_pages_dir .map(|custom_dir| custom_dir.join(patch_name)) .filter(|path| path.exists() && path.is_file()) } /// Search for a page and return the path to it. pub fn find_page( &self, name: &str, languages: &[String], custom_pages_dir: Option<&Path>, platforms: &[PlatformType], ) -> Option { let page_filename = format!("{name}.md"); let patch_filename = format!("{name}.patch.md"); let custom_filename = format!("{name}.page.md"); // Determine directory paths let pages_dir = self.pages_dir(); let lang_dirs: Vec = languages .iter() .map(|lang| { if lang == "en" { String::from("pages") } else { format!("pages.{lang}") } }) .collect(); // Look up custom page (.page.md). If it exists, return it directly if let Some(config_dir) = custom_pages_dir { // TODO: Remove this check 1 year after version 1.7.0 was released self.check_for_old_custom_pages(config_dir); let custom_page = config_dir.join(custom_filename); if custom_page.exists() && custom_page.is_file() { return Some(PageLookupResult::with_page(custom_page)); } } let patch_path = Self::find_patch(&patch_filename, custom_pages_dir); // Try to find a platform specific path next, in the order supplied by the user, and append custom patch to it. for &platform in platforms { let platform_dir = Cache::get_platform_dir(platform); if let Some(page) = Self::find_page_for_platform(&page_filename, &pages_dir, platform_dir, &lang_dirs) { return Some(PageLookupResult::with_page(page).with_optional_patch(patch_path)); } } // Did not find platform specific results, fall back to "common" Self::find_page_for_platform(&page_filename, &pages_dir, "common", &lang_dirs) .map(|page| PageLookupResult::with_page(page).with_optional_patch(patch_path)) } /// Return the available pages. pub fn list_pages( &self, custom_pages_dir: Option<&Path>, platforms: &[PlatformType], ) -> Vec { // Determine platforms directory and platform let platforms_dir = self.pages_dir().join("pages"); let platform_dirs: Vec<&'static str> = platforms .iter() .map(|&p| Self::get_platform_dir(p)) .collect(); // Closure that allows the WalkDir instance to traverse platform // specific and common page directories, but not others. let should_walk = |entry: &DirEntry| -> bool { let file_type = entry.file_type(); let Some(file_name) = entry.file_name().to_str() else { return false; }; if file_type.is_dir() { return file_name == "common" || platform_dirs.contains(&file_name); } else if file_type.is_file() { return true; } false }; let to_stem = |entry: DirEntry| -> Option { entry .path() .file_stem() .and_then(OsStr::to_str) .map(str::to_string) }; let to_stem_custom = |entry: DirEntry| -> Option { entry .path() .file_name() .and_then(OsStr::to_str) .and_then(|s| s.strip_suffix(".page.md")) .map(str::to_string) }; // Recursively walk through common and (if applicable) platform specific directory let mut pages = WalkDir::new(platforms_dir) .min_depth(1) // Skip root directory .into_iter() .filter_entry(should_walk) // Filter out pages for other architectures .filter_map(Result::ok) // Convert results to options, filter out errors .filter_map(|e| { let extension = e.path().extension().unwrap_or_default(); if e.file_type().is_file() && extension == "md" { to_stem(e) } else { None } }) .collect::>(); if let Some(custom_pages_dir) = custom_pages_dir { let is_page = |entry: &DirEntry| -> bool { entry.file_type().is_file() && entry .path() .file_name() .and_then(OsStr::to_str) .map_or(false, |file_name| file_name.ends_with(".page.md")) }; let custom_pages = WalkDir::new(custom_pages_dir) .min_depth(1) .max_depth(1) .into_iter() .filter_entry(is_page) .filter_map(Result::ok) .filter_map(to_stem_custom); pages.extend(custom_pages); } pages.sort(); pages.dedup(); pages } /// Delete the cache directory /// /// Returns true if the cache was deleted and false if the cache dir did /// not exist. pub fn clear(&self) -> Result { if !self.cache_dir.exists() { return Ok(false); } ensure!( self.cache_dir.is_dir(), "Cache path ({}) is not a directory.", self.cache_dir.display(), ); // Delete old tldr-pages cache location as well if present // TODO: To be removed in the future for pages_dir_name in [TLDR_PAGES_DIR, TLDR_OLD_PAGES_DIR] { let pages_dir = self.cache_dir.join(pages_dir_name); if pages_dir.exists() { fs::remove_dir_all(&pages_dir).with_context(|| { format!( "Could not remove the cache directory at {}", pages_dir.display() ) })?; } } Ok(true) } /// Check for old custom pages (without .md suffix) and print a warning. fn check_for_old_custom_pages(&self, custom_pages_dir: &Path) { let old_custom_pages_exist = WalkDir::new(custom_pages_dir) .min_depth(1) .max_depth(1) .into_iter() .filter_entry(|entry| entry.file_type().is_file()) .any(|entry| { if let Ok(entry) = entry { let extension = entry.path().extension(); if let Some(extension) = extension { extension == "page" || extension == "patch" } else { false } } else { false } }); if old_custom_pages_exist { print_warning( self.enable_styles, &format!( "Custom pages using the old naming convention were found in {}.\n\ Please rename them to follow the new convention:\n\ - `.page` → `.page.md`\n\ - `.patch` → `.patch.md`", custom_pages_dir.display() ), ); } } } /// Unit Tests for cache module #[cfg(test)] mod tests { use super::*; use std::{ fs::File, io::{Read, Write}, }; #[test] fn test_reader_with_patch() { // Write test files let dir = tempfile::tempdir().unwrap(); let page_path = dir.path().join("test.page.md"); let patch_path = dir.path().join("test.patch.md"); { let mut f1 = File::create(&page_path).unwrap(); f1.write_all(b"Hello\n").unwrap(); let mut f2 = File::create(&patch_path).unwrap(); f2.write_all(b"World").unwrap(); } // Create chained reader from lookup result let lr = PageLookupResult::with_page(page_path).with_optional_patch(Some(patch_path)); let mut reader = lr.reader().unwrap(); // Read into a Vec let mut buf = Vec::new(); reader.read_to_end(&mut buf).unwrap(); assert_eq!(&buf, b"Hello\n\nWorld"); } #[test] fn test_reader_without_patch() { // Write test file let dir = tempfile::tempdir().unwrap(); let page_path = dir.path().join("test.page.md"); { let mut f = File::create(&page_path).unwrap(); f.write_all(b"Hello\n").unwrap(); } // Create chained reader from lookup result let lr = PageLookupResult::with_page(page_path); let mut reader = lr.reader().unwrap(); // Read into a Vec let mut buf = Vec::new(); reader.read_to_end(&mut buf).unwrap(); assert_eq!(&buf, b"Hello\n"); } } tealdeer-1.7.0/src/cli.rs000064400000000000000000000056361046102023000133340ustar 00000000000000//! Definition of the CLI arguments and options. use std::path::PathBuf; use clap::{arg, builder::ArgAction, command, ArgGroup, Parser}; use crate::types::{ColorOptions, PlatformType}; // Note: flag names are specified explicitly in clap attributes // to improve readability and allow contributors to grep names like "clear-cache" #[derive(Parser, Debug)] #[command( about = "A fast TLDR client", version, disable_version_flag = true, author, help_template = "{before-help}{name} {version}: {about-with-newline}{author-with-newline} {usage-heading} {usage} {all-args}{after-help}", after_help = "To view the user documentation, please visit https://tealdeer-rs.github.io/tealdeer/.", arg_required_else_help = true, help_expected = true, group = ArgGroup::new("command_or_file").args(&["command", "render"]), )] pub(crate) struct Cli { /// The command to show (e.g. `tar` or `git log`) #[arg(num_args(1..))] pub command: Vec, /// List all commands in the cache #[arg(short = 'l', long = "list")] pub list: bool, /// Render a specific markdown file #[arg( short = 'f', long = "render", value_name = "FILE", conflicts_with = "command" )] pub render: Option, /// Override the operating system, can be specified multiple times in order of preference #[arg( short = 'p', long = "platform", value_name = "PLATFORM", action = ArgAction::Append, )] pub platforms: Option>, /// Override the language #[arg(short = 'L', long = "language")] pub language: Option, /// Update the local cache #[arg(short = 'u', long = "update")] pub update: bool, /// If auto update is configured, disable it for this run #[arg(long = "no-auto-update", requires = "command_or_file")] pub no_auto_update: bool, /// Clear the local cache #[arg(short = 'c', long = "clear-cache")] pub clear_cache: bool, /// Use a pager to page output #[arg(long = "pager", requires = "command_or_file")] pub pager: bool, /// Display the raw markdown instead of rendering it #[arg(short = 'r', long = "raw", requires = "command_or_file")] pub raw: bool, /// Suppress informational messages #[arg(short = 'q', long = "quiet")] pub quiet: bool, /// Show file and directory paths used by tealdeer #[arg(long = "show-paths")] pub show_paths: bool, /// Create a basic config #[arg(long = "seed-config")] pub seed_config: bool, /// Control whether to use color #[arg(long = "color", value_name = "WHEN")] pub color: Option, /// Print the version // Note: We override the version flag because clap uses `-V` by default, // while TLDR specification requires `-v` to be used. #[arg(short = 'v', long = "version", action = ArgAction::Version)] pub version: (), } tealdeer-1.7.0/src/config.rs000064400000000000000000000346211046102023000140260ustar 00000000000000use std::{ env, fmt, fs, io::{Read, Write}, path::{Path, PathBuf}, time::Duration, }; use anyhow::{bail, ensure, Context, Result}; use app_dirs::{get_app_root, AppDataType}; use log::debug; use serde_derive::{Deserialize, Serialize}; use yansi::{Color, Style}; use crate::types::PathSource; pub const CONFIG_FILE_NAME: &str = "config.toml"; pub const MAX_CACHE_AGE: Duration = Duration::from_secs(2_592_000); // 30 days const DEFAULT_UPDATE_INTERVAL_HOURS: u64 = MAX_CACHE_AGE.as_secs() / 3600; // 30 days fn default_underline() -> bool { false } fn default_bold() -> bool { false } fn default_italic() -> bool { false } #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "lowercase")] pub enum RawColor { Black, Red, Green, Yellow, Blue, Magenta, Purple, // Backwards compatibility with ansi_term (until tealdeer 1.5.0) Cyan, White, Ansi(u8), Rgb { r: u8, g: u8, b: u8 }, } impl From for Color { fn from(raw_color: RawColor) -> Self { match raw_color { RawColor::Black => Self::Black, RawColor::Red => Self::Red, RawColor::Green => Self::Green, RawColor::Yellow => Self::Yellow, RawColor::Blue => Self::Blue, RawColor::Magenta | RawColor::Purple => Self::Magenta, RawColor::Cyan => Self::Cyan, RawColor::White => Self::White, RawColor::Ansi(num) => Self::Fixed(num), RawColor::Rgb { r, g, b } => Self::RGB(r, g, b), } } } #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] struct RawStyle { pub foreground: Option, pub background: Option, #[serde(default = "default_underline")] pub underline: bool, #[serde(default = "default_bold")] pub bold: bool, #[serde(default = "default_italic")] pub italic: bool, } #[allow(clippy::derivable_impls)] // Explicitly control defaults impl Default for RawStyle { fn default() -> Self { Self { foreground: None, background: None, underline: false, bold: false, italic: false, } } } impl From for Style { fn from(raw_style: RawStyle) -> Self { let mut style = Self::default(); if let Some(foreground) = raw_style.foreground { style = style.fg(Color::from(foreground)); } if let Some(background) = raw_style.background { style = style.bg(Color::from(background)); } if raw_style.underline { style = style.underline(); } if raw_style.bold { style = style.bold(); } if raw_style.italic { style = style.italic(); } style } } #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] struct RawStyleConfig { #[serde(default)] pub description: RawStyle, #[serde(default)] pub command_name: RawStyle, #[serde(default)] pub example_text: RawStyle, #[serde(default)] pub example_code: RawStyle, #[serde(default)] pub example_variable: RawStyle, } impl From for StyleConfig { fn from(raw_style_config: RawStyleConfig) -> Self { Self { command_name: raw_style_config.command_name.into(), description: raw_style_config.description.into(), example_text: raw_style_config.example_text.into(), example_code: raw_style_config.example_code.into(), example_variable: raw_style_config.example_variable.into(), } } } #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] struct RawDisplayConfig { #[serde(default)] pub compact: bool, #[serde(default)] pub use_pager: bool, } impl From for DisplayConfig { fn from(raw_display_config: RawDisplayConfig) -> Self { Self { compact: raw_display_config.compact, use_pager: raw_display_config.use_pager, } } } /// Serde doesn't support default values yet (tracking issue: /// ), so we need to wrap /// `DEFAULT_UPDATE_INTERVAL_HOURS` in a function to be able to use /// `#[serde(default = ...)]` const fn default_auto_update_interval_hours() -> u64 { DEFAULT_UPDATE_INTERVAL_HOURS } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] struct RawUpdatesConfig { #[serde(default)] pub auto_update: bool, #[serde(default = "default_auto_update_interval_hours")] pub auto_update_interval_hours: u64, } impl Default for RawUpdatesConfig { fn default() -> Self { Self { auto_update: false, auto_update_interval_hours: DEFAULT_UPDATE_INTERVAL_HOURS, } } } impl From for UpdatesConfig { fn from(raw_updates_config: RawUpdatesConfig) -> Self { Self { auto_update: raw_updates_config.auto_update, auto_update_interval: Duration::from_secs( raw_updates_config.auto_update_interval_hours * 3600, ), } } } #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] struct RawDirectoriesConfig { #[serde(default)] pub cache_dir: Option, #[serde(default)] pub custom_pages_dir: Option, } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(default)] struct RawConfig { style: RawStyleConfig, display: RawDisplayConfig, updates: RawUpdatesConfig, directories: RawDirectoriesConfig, } impl RawConfig { fn new() -> Self { Self::default() } } impl Default for RawConfig { fn default() -> Self { let mut raw_config = RawConfig { style: RawStyleConfig::default(), display: RawDisplayConfig::default(), updates: RawUpdatesConfig::default(), directories: RawDirectoriesConfig::default(), }; // Set default config raw_config.style.example_text.foreground = Some(RawColor::Green); raw_config.style.command_name.foreground = Some(RawColor::Cyan); raw_config.style.example_code.foreground = Some(RawColor::Cyan); raw_config.style.example_variable.foreground = Some(RawColor::Cyan); raw_config.style.example_variable.underline = true; raw_config } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct StyleConfig { pub description: Style, pub command_name: Style, pub example_text: Style, pub example_code: Style, pub example_variable: Style, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct DisplayConfig { pub compact: bool, pub use_pager: bool, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct UpdatesConfig { pub auto_update: bool, pub auto_update_interval: Duration, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PathWithSource { pub path: PathBuf, pub source: PathSource, } impl PathWithSource { pub fn path(&self) -> &Path { &self.path } } impl fmt::Display for PathWithSource { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} ({})", self.path.display(), self.source) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct DirectoriesConfig { pub cache_dir: PathWithSource, pub custom_pages_dir: Option, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Config { pub style: StyleConfig, pub display: DisplayConfig, pub updates: UpdatesConfig, pub directories: DirectoriesConfig, } impl Config { /// Convert a `RawConfig` to a high-level `Config`. /// /// For this, some values need to be converted to other types and some /// defaults need to be set (sometimes based on env variables). fn from_raw(raw_config: RawConfig) -> Result { let style = raw_config.style.into(); let display = raw_config.display.into(); let updates = raw_config.updates.into(); // Determine directories config. For this, we need to take some // additional factory into account, like env variables, or the // user config. let cache_dir_env_var = "TEALDEER_CACHE_DIR"; let cache_dir = if let Ok(env_var) = env::var(cache_dir_env_var) { // For backwards compatibility reasons, the cache directory can be // overridden using an env variable. This is deprecated and will be // phased out in the future. eprintln!("Warning: The ${cache_dir_env_var} env variable is deprecated, use the `cache_dir` option in the config file instead."); PathWithSource { path: PathBuf::from(env_var), source: PathSource::EnvVar, } } else if let Some(config_value) = raw_config.directories.cache_dir { // If the user explicitly configured a cache directory, use that. PathWithSource { path: config_value, source: PathSource::ConfigFile, } } else if let Ok(default_dir) = get_app_root(AppDataType::UserCache, &crate::APP_INFO) { // Otherwise, fall back to the default user cache directory. PathWithSource { path: default_dir, source: PathSource::OsConvention, } } else { // If everything fails, give up bail!("Could not determine user cache directory"); }; let custom_pages_dir = raw_config .directories .custom_pages_dir .map(|path| PathWithSource { path, source: PathSource::ConfigFile, }) .or_else(|| { get_app_root(AppDataType::UserData, &crate::APP_INFO) .map(|path| { // Note: The `join("")` call ensures that there's a trailing slash PathWithSource { path: path.join("pages").join(""), source: PathSource::OsConvention, } }) .ok() }); let directories = DirectoriesConfig { cache_dir, custom_pages_dir, }; Ok(Self { style, display, updates, directories, }) } pub fn load(enable_styles: bool) -> Result { debug!("Loading config"); // Determine path let (config_file_path, _) = get_config_path().context("Could not determine config path")?; // Load raw config let raw_config: RawConfig = if config_file_path.exists() && config_file_path.is_file() { let mut config_file = fs::File::open(&config_file_path).with_context(|| { format!("Failed to open config file path at {:?}", &config_file_path) })?; let mut contents = String::new(); config_file.read_to_string(&mut contents).with_context(|| { format!("Failed to read from config file at {:?}", &config_file_path) })?; toml::from_str(&contents).with_context(|| { format!("Failed to parse TOML config file at {config_file_path:?}") })? } else { RawConfig::new() }; // Convert to config let mut config = Self::from_raw(raw_config).context("Could not process raw config")?; // Potentially override styles if !enable_styles { config.style = StyleConfig { command_name: Style::default(), description: Style::default(), example_text: Style::default(), example_code: Style::default(), example_variable: Style::default(), }; } Ok(config) } } /// Return the path to the config directory. /// /// The config dir path can be overridden using the `TEALDEER_CONFIG_DIR` env /// variable. Otherwise, the user config directory is returned. /// /// Note that this function does not verify whether the directory at that /// location exists, or is a directory. pub fn get_config_dir() -> Result<(PathBuf, PathSource)> { // Allow overriding the config directory by setting the // $TEALDEER_CONFIG_DIR env variable. if let Ok(value) = env::var("TEALDEER_CONFIG_DIR") { return Ok((PathBuf::from(value), PathSource::EnvVar)); }; // Otherwise, fall back to the user config directory. let dirs = get_app_root(AppDataType::UserConfig, &crate::APP_INFO) .context("Failed to determine the user config directory")?; Ok((dirs, PathSource::OsConvention)) } /// Return the path to the config file. /// /// Note that this function does not verify whether the file at that location /// exists, or is a file. pub fn get_config_path() -> Result<(PathBuf, PathSource)> { let (config_dir, source) = get_config_dir()?; let config_file_path = config_dir.join(CONFIG_FILE_NAME); Ok((config_file_path, source)) } /// Create default config file. pub fn make_default_config() -> Result { let (config_dir, _) = get_config_dir()?; // Ensure that config directory exists if config_dir.exists() { ensure!( config_dir.is_dir(), "Config directory could not be created: {} already exists but is not a directory", config_dir.to_string_lossy(), ); } else { fs::create_dir_all(&config_dir).context("Could not create config directory")?; } // Ensure that a config file doesn't get overwritten let config_file_path = config_dir.join(CONFIG_FILE_NAME); ensure!( !config_file_path.is_file(), "A configuration file already exists at {}, no action was taken.", config_file_path.to_str().unwrap() ); // Create default config let serialized_config = toml::to_string(&RawConfig::new()).context("Failed to serialize default config")?; // Write default config let mut config_file = fs::File::create(&config_file_path).context("Could not create config file")?; let _wc = config_file .write(serialized_config.as_bytes()) .context("Could not write to config file")?; Ok(config_file_path) } #[test] fn test_serialize_deserialize() { let raw_config = RawConfig::new(); let serialized = toml::to_string(&raw_config).unwrap(); let deserialized: RawConfig = toml::from_str(&serialized).unwrap(); assert_eq!(raw_config, deserialized); } tealdeer-1.7.0/src/extensions.rs000064400000000000000000000016571046102023000147630ustar 00000000000000use std::mem; /// An extension trait to clear duplicates from a collection. pub(crate) trait Dedup { fn clear_duplicates(&mut self); } /// Clear duplicates from a collection, keep the first one seen. /// /// For small vectors, this will be faster than a `HashSet`. impl Dedup for Vec { fn clear_duplicates(&mut self) { let orig = mem::replace(self, Vec::with_capacity(self.len())); for item in orig { if !self.contains(&item) { self.push(item); } } } } /// Like `str::find`, but starts searching at `start`. pub(crate) trait FindFrom { fn find_from(&self, needle: &Self, start: usize) -> Option; } impl FindFrom for str { fn find_from(&self, needle: &Self, start: usize) -> Option { self.get(start..) .and_then(|s| s.find(needle)) .map(|i| i + start) } } tealdeer-1.7.0/src/formatter.rs000064400000000000000000000176531046102023000145720ustar 00000000000000//! Functions related to formatting and printing lines from a `Tokenizer`. use log::debug; use crate::{extensions::FindFrom, types::LineType}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// Represents a snippet from a page of a specific highlighting class. pub enum PageSnippet<'a> { CommandName(&'a str), Variable(&'a str), NormalCode(&'a str), Description(&'a str), Text(&'a str), Linebreak, } impl<'a> PageSnippet<'a> { pub fn is_empty(&self) -> bool { use PageSnippet::*; match self { CommandName(s) | Variable(s) | NormalCode(s) | Description(s) | Text(s) => s.is_empty(), Linebreak => false, } } } /// Parse the content of each line yielded by `lines` and yield `HighLightingSnippet`s accordingly. pub fn highlight_lines( lines: L, process_snippet: &mut F, keep_empty_lines: bool, ) -> Result<(), E> where L: Iterator, F: for<'snip> FnMut(PageSnippet<'snip>) -> Result<(), E>, { let mut command = String::new(); for line in lines { match line { LineType::Empty => { if keep_empty_lines { process_snippet(PageSnippet::Linebreak)?; } } LineType::Title(title) => { debug!("Ignoring title"); // This is safe as long as the parsed title is only the command, // and the iterator yields values in order of appearance. command = title; debug!("Detected command name: {}", &command); } LineType::Description(text) => process_snippet(PageSnippet::Description(&text))?, LineType::ExampleText(text) => process_snippet(PageSnippet::Text(&text))?, LineType::ExampleCode(text) => { process_snippet(PageSnippet::NormalCode(" "))?; highlight_code(&command, &text, process_snippet)?; process_snippet(PageSnippet::Linebreak)?; } LineType::Other(text) => debug!("Unknown line type: {:?}", text), } } process_snippet(PageSnippet::Linebreak)?; Ok(()) } /// Highlight code examples including user variables in {{ curly braces }}. fn highlight_code<'a, E>( command: &'a str, text: &'a str, process_snippet: &mut impl FnMut(PageSnippet<'a>) -> Result<(), E>, ) -> Result<(), E> { let variable_splits = text .split("}}") .map(|s| s.split_once("{{").unwrap_or((s, ""))); for (code_segment, variable) in variable_splits { highlight_code_segment(command, code_segment, process_snippet)?; process_snippet(PageSnippet::Variable(variable))?; } Ok(()) } /// Yields `NormalCode` and `CommandName` in alternating order according to the occurrences of /// `command_name` in `segment`. Variables are not detected here, see `highlight_code` /// instead. fn highlight_code_segment<'a, E>( command_name: &'a str, mut segment: &'a str, process_snippet: &mut impl FnMut(PageSnippet<'a>) -> Result<(), E>, ) -> Result<(), E> { if !command_name.is_empty() { let mut search_start = 0; while let Some(match_start) = segment.find_from(command_name, search_start) { let match_end = match_start + command_name.len(); if is_freestanding_substring(segment, (match_start, match_end)) { process_snippet(PageSnippet::NormalCode(&segment[..match_start]))?; process_snippet(PageSnippet::CommandName(command_name))?; segment = &segment[match_end..]; search_start = 0; } else { search_start = segment[match_start..] .char_indices() .nth(1) .map_or(segment.len(), |(i, _)| match_start + i); } } } process_snippet(PageSnippet::NormalCode(segment))?; Ok(()) } /// Checks whether the characters right before and after the substring (given by half-open index interval) are whitespace (if they exist). fn is_freestanding_substring(surrounding: &str, substring: (usize, usize)) -> bool { let (start, end) = substring; // "okay" meaning or let char_before_is_okay = surrounding[..start] .chars() .last() .filter(|prev_char| !prev_char.is_whitespace()) .is_none(); let char_after_is_okay = surrounding[end..] .chars() .next() .filter(|next_char| !next_char.is_whitespace()) .is_none(); char_before_is_okay && char_after_is_okay } #[cfg(test)] mod tests { use super::*; use PageSnippet::*; #[test] fn test_is_freestanding_substring() { assert!(is_freestanding_substring("I love tldr", (0, 1))); assert!(is_freestanding_substring("I love tldr", (2, 6))); assert!(is_freestanding_substring("I love tldr", (7, 11))); assert!(is_freestanding_substring("tldr", (0, 4))); assert!(is_freestanding_substring("tldr ", (0, 4))); assert!(is_freestanding_substring(" tldr", (1, 5))); assert!(is_freestanding_substring(" tldr ", (1, 5))); assert!(!is_freestanding_substring("tldr", (1, 3))); assert!(!is_freestanding_substring("tldr ", (1, 4))); assert!(!is_freestanding_substring(" tldr", (1, 4))); assert!(is_freestanding_substring( " épicé ", (1, " épicé".len()) // note the missing trailing space )); assert!(!is_freestanding_substring( " épicé ", (1, " épic".len()) // note the missing trailing space and character )); } fn run<'a>(cmd: &'a str, segment: &'a str) -> Vec> { let mut yielded = Vec::new(); let mut process_snippet = |snip: PageSnippet<'a>| { if !snip.is_empty() { yielded.push(snip); } Ok::<(), ()>(()) }; highlight_code_segment(cmd, segment, &mut process_snippet) .expect("highlight code segment failed"); yielded } #[test] fn test_highlight_code_segment() { assert!(run("make", "").is_empty()); assert_eq!( &run("make", "make all CC=clang -q"), &[CommandName("make"), NormalCode(" all CC=clang -q")] ); assert_eq!( &run("make", " make money --always-make"), &[ NormalCode(" "), CommandName("make"), NormalCode(" money --always-make") ] ); assert_eq!( &run("git commit", "git commit -m 'git commit'"), &[CommandName("git commit"), NormalCode(" -m 'git commit'"),] ); } #[test] fn test_i18n() { assert_eq!( &run("mäke", "mäke höhlenrätselbücher"), &[CommandName("mäke"), NormalCode(" höhlenrätselbücher")] ); assert_eq!( &run( "Müll", "1000 Gründe warum Müll heute größer ist als Müll früher, ärgerlich" ), &[ NormalCode("1000 Gründe warum "), CommandName("Müll"), NormalCode(" heute größer ist als "), CommandName("Müll"), NormalCode(" früher, ärgerlich") ] ); assert_eq!( &run( "übergang", "die Zustandsübergangsfunktion übergang Änderungen", ), &[ NormalCode("die Zustandsübergangsfunktion "), CommandName("übergang"), NormalCode(" Änderungen") ], ); } #[test] fn test_empty_command() { let segment = "some code"; let snippets = [NormalCode(segment)]; assert_eq!(run("", segment), snippets); assert_eq!(run(" ", segment), snippets); assert_eq!(run(" \t ", segment), snippets); } } tealdeer-1.7.0/src/line_iterator.rs000064400000000000000000000074671046102023000154310ustar 00000000000000//! Code to split a `BufRead` instance into an iterator of `LineType`s. use std::io::{BufRead, Read}; use log::warn; use crate::types::LineType; #[derive(Debug, PartialEq, Eq)] pub enum TldrFormat { /// Not yet clear Undecided, /// The original format V1, /// The new format (see ) V2, } /// A `LineIterator` is initialized with a `BufReader` instance that contains the /// entire Tldr page. It then implements `Iterator`. #[derive(Debug)] pub struct LineIterator { /// An instance of `R: BufRead`. reader: R, /// Whether the first line has already been processed or not. first_line: bool, /// Buffer for the current line. Used internally. current_line: String, /// The tldr page format. format: TldrFormat, } impl LineIterator where R: BufRead, { pub fn new(reader: R) -> Self { Self { reader, first_line: true, current_line: String::new(), format: TldrFormat::Undecided, } } } impl Iterator for LineIterator { type Item = LineType; fn next(&mut self) -> Option { self.current_line.clear(); let bytes_read = self.reader.read_line(&mut self.current_line); match bytes_read { Ok(0) => None, Err(e) => { warn!("Could not read line from reader: {:?}", e); None } Ok(_) => { // Handle new titles if self.first_line { if self.current_line.starts_with('#') { // It's the old format. self.format = TldrFormat::V1; } else { // It's the new format! Drop next line. if let Err(e) = Read::bytes(&mut self.reader) .find(|b| matches!(b, Ok(b'\n') | Err(_))) .transpose() { warn!("Could not read line from reader: {:?}", e); return None; } self.first_line = false; self.format = TldrFormat::V2; return Some(LineType::Title(self.current_line.trim_end().to_string())); } } self.first_line = false; // Convert line to a `LineType` instance match self.format { TldrFormat::V1 => Some(LineType::from_v1(&self.current_line[..])), TldrFormat::V2 => Some(LineType::from(&self.current_line[..])), TldrFormat::Undecided => panic!("Could not determine page format version"), } } } } } #[cfg(test)] mod test { use super::LineIterator; use crate::types::LineType; #[test] fn test_first_line_old_format() { let input = "# The Title\n> Description\n"; let mut lines = LineIterator::new(input.as_bytes()); let title = lines.next().unwrap(); assert_eq!(title, LineType::Title("The Title".to_string())); let description = lines.next().unwrap(); assert_eq!( description, LineType::Description("Description".to_string()) ); } #[test] fn test_first_line_new_format() { let input = "The Title\n=========\n> Description\n"; let mut lines = LineIterator::new(input.as_bytes()); let title = lines.next().unwrap(); assert_eq!(title, LineType::Title("The Title".to_string())); let description = lines.next().unwrap(); assert_eq!( description, LineType::Description("Description".to_string()) ); } } tealdeer-1.7.0/src/main.rs000064400000000000000000000331021046102023000134760ustar 00000000000000//! An implementation of [tldr](https://github.com/tldr-pages/tldr) in Rust. // // Copyright (c) 2015-2021 tealdeer developers // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. All files in the project carrying such notice may not be // copied, modified, or distributed except according to those terms. #![deny(clippy::all)] #![warn(clippy::pedantic)] #![allow(clippy::enum_glob_use)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::similar_names)] #![allow(clippy::struct_excessive_bools)] #![allow(clippy::too_many_lines)] #[cfg(any( all(feature = "native-roots", feature = "webpki-roots"), all(feature = "native-roots", feature = "native-tls"), all(feature = "webpki-roots", feature = "native-tls"), not(any( feature = "native-roots", feature = "webpki-roots", feature = "native-tls" )), ))] compile_error!( "exactly one of the features \"native-roots\", \"webpki-roots\" or \"native-tls\" must be enabled" ); use std::{ env, io::{self, IsTerminal}, process, }; use app_dirs::AppInfo; use clap::Parser; mod cache; mod cli; mod config; pub mod extensions; mod formatter; mod line_iterator; mod output; mod types; mod utils; use crate::{ cache::{Cache, CacheFreshness, PageLookupResult, TLDR_PAGES_DIR}, cli::Cli, config::{get_config_dir, get_config_path, make_default_config, Config, PathWithSource}, extensions::Dedup, output::print_page, types::{ColorOptions, PlatformType}, utils::{print_error, print_warning}, }; const NAME: &str = "tealdeer"; const APP_INFO: AppInfo = AppInfo { name: NAME, author: NAME, }; const ARCHIVE_URL: &str = "https://tldr.sh/assets/tldr.zip"; /// The cache should be updated if it was explicitly requested, /// or if an automatic update is due and allowed. fn should_update_cache(cache: &Cache, args: &Cli, config: &Config) -> bool { args.update || (!args.no_auto_update && config.updates.auto_update && cache .last_update() .map_or(true, |ago| ago >= config.updates.auto_update_interval)) } #[derive(PartialEq)] enum CheckCacheResult { CacheFound, CacheMissing, } /// Check the cache for freshness. If it's stale or missing, show a warning. fn check_cache(cache: &Cache, args: &Cli, enable_styles: bool) -> CheckCacheResult { match cache.freshness() { CacheFreshness::Fresh => CheckCacheResult::CacheFound, CacheFreshness::Stale(_) if args.quiet => CheckCacheResult::CacheFound, CacheFreshness::Stale(age) => { print_warning( enable_styles, &format!( "The cache hasn't been updated for {} days.\n\ You should probably run `tldr --update` soon.", age.as_secs() / 24 / 3600 ), ); CheckCacheResult::CacheFound } CacheFreshness::Missing => { print_error( enable_styles, &anyhow::anyhow!( "Page cache not found. Please run `tldr --update` to download the cache." ), ); println!("\nNote: You can optionally enable automatic cache updates by adding the"); println!("following config to your config file:\n"); println!(" [updates]"); println!(" auto_update = true\n"); println!("The path to your config file can be looked up with `tldr --show-paths`."); println!("To create an initial config file, use `tldr --seed-config`.\n"); println!("You can find more tips and tricks in our docs:\n"); println!(" https://tealdeer-rs.github.io/tealdeer/config_updates.html"); CheckCacheResult::CacheMissing } } } /// Clear the cache fn clear_cache(cache: &Cache, quietly: bool, enable_styles: bool) { let cache_dir_found = cache.clear().unwrap_or_else(|e| { print_error(enable_styles, &e.context("Could not clear cache")); process::exit(1); }); if !quietly { let cache_dir = cache.cache_dir().display(); if cache_dir_found { eprintln!("Successfully cleared cache at `{cache_dir}`."); } else { eprintln!("Cache directory not found at `{cache_dir}`, nothing to do."); } } } /// Update the cache fn update_cache(cache: &Cache, quietly: bool, enable_styles: bool) { cache.update(ARCHIVE_URL).unwrap_or_else(|e| { print_error(enable_styles, &e.context("Could not update cache")); process::exit(1); }); if !quietly { eprintln!("Successfully updated cache."); } } /// Show file paths fn show_paths(config: &Config) { let config_dir = get_config_dir().map_or_else( |e| format!("[Error: {e}]"), |(mut path, source)| { path.push(""); // Trailing path separator match path.to_str() { Some(path) => format!("{path} ({source})"), None => "[Invalid]".to_string(), } }, ); let config_path = get_config_path().map_or_else( |e| format!("[Error: {e}]"), |(path, _)| path.display().to_string(), ); let cache_dir = config.directories.cache_dir.to_string(); let pages_dir = { let mut path = config.directories.cache_dir.path.clone(); path.push(TLDR_PAGES_DIR); path.push(""); // Trailing path separator path.display().to_string() }; let custom_pages_dir = match config.directories.custom_pages_dir { Some(ref path_with_source) => path_with_source.to_string(), None => "[None]".to_string(), }; println!("Config dir: {config_dir}"); println!("Config path: {config_path}"); println!("Cache dir: {cache_dir}"); println!("Pages dir: {pages_dir}"); println!("Custom pages dir: {custom_pages_dir}"); } /// Create seed config file and exit fn create_config_and_exit(enable_styles: bool) { match make_default_config() { Ok(config_file_path) => { eprintln!( "Successfully created seed config file here: {}", config_file_path.to_str().unwrap() ); process::exit(0); } Err(e) => { print_error(enable_styles, &e.context("Could not create seed config")); process::exit(1); } } } #[cfg(feature = "logging")] fn init_log() { env_logger::init(); } #[cfg(not(feature = "logging"))] fn init_log() {} fn get_languages(env_lang: Option<&str>, env_language: Option<&str>) -> Vec { // Language list according to // https://github.com/tldr-pages/tldr/blob/main/CLIENT-SPECIFICATION.md#language if env_lang.is_none() { return vec!["en".to_string()]; } let env_lang = env_lang.unwrap(); // Create an iterator that contains $LANGUAGE (':' separated list) followed by $LANG (single language) let locales = env_language.unwrap_or("").split(':').chain([env_lang]); let mut lang_list = Vec::new(); for locale in locales { // Language plus country code (e.g. `en_US`) if locale.len() >= 5 && locale.chars().nth(2) == Some('_') { lang_list.push(&locale[..5]); } // Language code only (e.g. `en`) if locale.len() >= 2 && locale != "POSIX" { lang_list.push(&locale[..2]); } } lang_list.push("en"); lang_list.clear_duplicates(); lang_list.into_iter().map(str::to_string).collect() } fn get_languages_from_env() -> Vec { get_languages( std::env::var("LANG").ok().as_deref(), std::env::var("LANGUAGE").ok().as_deref(), ) } fn main() { // Initialize logger init_log(); // Parse arguments let args = Cli::parse(); // Determine the usage of styles #[cfg(target_os = "windows")] let ansi_support = yansi::Paint::enable_windows_ascii(); #[cfg(not(target_os = "windows"))] let ansi_support = true; let enable_styles = match args.color.unwrap_or_default() { // Attempt to use styling if instructed ColorOptions::Always => true, // Enable styling if: // * There is `ansi_support` // * NO_COLOR env var isn't set: https://no-color.org/ // * The output stream is stdout (not being piped) ColorOptions::Auto => { ansi_support && env::var_os("NO_COLOR").is_none() && io::stdout().is_terminal() } // Disable styling ColorOptions::Never => false, }; // Look up config file, if none is found fall back to default config. let config = match Config::load(enable_styles) { Ok(config) => config, Err(e) => { print_error(enable_styles, &e.context("Could not load config")); process::exit(1); } }; // Show various paths if args.show_paths { show_paths(&config); } // Create a basic config and exit if args.seed_config { create_config_and_exit(enable_styles); } let fallback_platforms: &[PlatformType] = &[PlatformType::current()]; let platforms = args .platforms .as_ref() .map_or(fallback_platforms, Vec::as_slice); // If a local file was passed in, render it and exit if let Some(file) = args.render { let path = PageLookupResult::with_page(file); if let Err(ref e) = print_page(&path, args.raw, enable_styles, args.pager, &config) { print_error(enable_styles, e); process::exit(1); } else { process::exit(0); }; } // Instantiate cache. This will not yet create the cache directory! let cache = Cache::new(&config.directories.cache_dir.path, enable_styles); // Clear cache, pass through if args.clear_cache { clear_cache(&cache, args.quiet, enable_styles); } // Cache update, pass through let cache_updated = if should_update_cache(&cache, &args, &config) { update_cache(&cache, args.quiet, enable_styles); true } else { false }; // Check cache presence and freshness if !cache_updated && (args.list || !args.command.is_empty()) && check_cache(&cache, &args, enable_styles) == CheckCacheResult::CacheMissing { process::exit(1); } // List cached commands and exit if args.list { let custom_pages_dir = config .directories .custom_pages_dir .as_ref() .map(PathWithSource::path); println!( "{}", cache.list_pages(custom_pages_dir, platforms).join("\n") ); process::exit(0); } // Show command from cache if !args.command.is_empty() { // Note: According to the TLDR client spec, page names must be transparently // lowercased before lookup: // https://github.com/tldr-pages/tldr/blob/main/CLIENT-SPECIFICATION.md#page-names let command = args.command.join("-").to_lowercase(); // Collect languages let languages = args .language .map_or_else(get_languages_from_env, |lang| vec![lang]); // Search for command in cache if let Some(lookup_result) = cache.find_page( &command, &languages, config .directories .custom_pages_dir .as_ref() .map(PathWithSource::path), platforms, ) { if let Err(ref e) = print_page(&lookup_result, args.raw, enable_styles, args.pager, &config) { print_error(enable_styles, e); process::exit(1); } process::exit(0); } else { if !args.quiet { print_warning( enable_styles, &format!( "Page `{}` not found in cache.\n\ Try updating with `tldr --update`, or submit a pull request to:\n\ https://github.com/tldr-pages/tldr", &command ), ); } process::exit(1); } } } #[cfg(test)] mod test { use crate::get_languages; mod language { use super::*; #[test] fn missing_lang_env() { let lang_list = get_languages(None, Some("de:fr")); assert_eq!(lang_list, ["en"]); let lang_list = get_languages(None, None); assert_eq!(lang_list, ["en"]); } #[test] fn missing_language_env() { let lang_list = get_languages(Some("de"), None); assert_eq!(lang_list, ["de", "en"]); } #[test] fn preference_order() { let lang_list = get_languages(Some("de"), Some("fr:cn")); assert_eq!(lang_list, ["fr", "cn", "de", "en"]); } #[test] fn country_code_expansion() { let lang_list = get_languages(Some("pt_BR"), None); assert_eq!(lang_list, ["pt_BR", "pt", "en"]); } #[test] fn ignore_posix_and_c() { let lang_list = get_languages(Some("POSIX"), None); assert_eq!(lang_list, ["en"]); let lang_list = get_languages(Some("C"), None); assert_eq!(lang_list, ["en"]); } #[test] fn no_duplicates() { let lang_list = get_languages(Some("de"), Some("fr:de:cn:de")); assert_eq!(lang_list, ["fr", "de", "cn", "en"]); } } } tealdeer-1.7.0/src/output.rs000064400000000000000000000054641046102023000141240ustar 00000000000000//! Functions for printing pages to the terminal use std::io::{self, BufRead, Write}; use anyhow::{Context, Result}; use crate::{ cache::PageLookupResult, config::{Config, StyleConfig}, formatter::{highlight_lines, PageSnippet}, line_iterator::LineIterator, }; /// Set up display pager /// /// SAFETY: this function may be called multiple times #[cfg(not(target_os = "windows"))] fn configure_pager(_: bool) { use std::sync::Once; static INIT: Once = Once::new(); INIT.call_once(|| pager::Pager::with_default_pager("less -R").setup()); } #[cfg(target_os = "windows")] fn configure_pager(enable_styles: bool) { use crate::utils::print_warning; print_warning(enable_styles, "--pager flag not available on Windows!"); } /// Print page by path pub fn print_page( lookup_result: &PageLookupResult, enable_markdown: bool, enable_styles: bool, use_pager: bool, config: &Config, ) -> Result<()> { // Create reader from file(s) let reader = lookup_result.reader()?; // Configure pager if applicable if use_pager || config.display.use_pager { configure_pager(enable_styles); } // Lock stdout only once, this improves performance considerably let stdout = io::stdout(); let mut handle = stdout.lock(); if enable_markdown { // Print the raw markdown of the file. for line in reader.lines() { let line = line.context("Error while reading from a page")?; writeln!(handle, "{line}").context("Could not write to stdout")?; } } else { // Closure that processes a page snippet and writes it to stdout let mut process_snippet = |snip: PageSnippet<'_>| { if snip.is_empty() { Ok(()) } else { print_snippet(&mut handle, snip, &config.style).context("Failed to print snippet") } }; // Print highlighted lines highlight_lines( LineIterator::new(reader), &mut process_snippet, !config.display.compact, ) .context("Could not write to stdout")?; }; // We're done outputting data, flush stdout now! handle.flush().context("Could not flush stdout")?; Ok(()) } fn print_snippet( writer: &mut impl Write, snip: PageSnippet<'_>, style: &StyleConfig, ) -> io::Result<()> { use PageSnippet::*; match snip { CommandName(s) => write!(writer, "{}", style.command_name.paint(s)), Variable(s) => write!(writer, "{}", style.example_variable.paint(s)), NormalCode(s) => write!(writer, "{}", style.example_code.paint(s)), Description(s) => writeln!(writer, " {}", style.description.paint(s)), Text(s) => writeln!(writer, " {}", style.example_text.paint(s)), Linebreak => writeln!(writer), } } tealdeer-1.7.0/src/types.rs000064400000000000000000000153631046102023000137270ustar 00000000000000//! Shared types used in tealdeer. use std::{fmt, str}; use serde_derive::{Deserialize, Serialize}; #[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] #[allow(dead_code)] pub enum PlatformType { Linux, OsX, Windows, SunOs, Android, FreeBsd, NetBsd, OpenBsd, } impl fmt::Display for PlatformType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Linux => write!(f, "Linux"), Self::OsX => write!(f, "macOS / BSD"), Self::Windows => write!(f, "Windows"), Self::SunOs => write!(f, "SunOS"), Self::Android => write!(f, "Android"), Self::FreeBsd => write!(f, "FreeBSD"), Self::NetBsd => write!(f, "NetBSD"), Self::OpenBsd => write!(f, "OpenBSD"), } } } impl clap::ValueEnum for PlatformType { fn value_variants<'a>() -> &'a [Self] { &[ Self::Linux, Self::OsX, Self::SunOs, Self::Windows, Self::Android, Self::FreeBsd, Self::NetBsd, Self::OpenBsd, ] } fn to_possible_value<'a>(&self) -> Option { match self { Self::Linux => Some(clap::builder::PossibleValue::new("linux")), Self::OsX => Some(clap::builder::PossibleValue::new("macos").alias("osx")), Self::Windows => Some(clap::builder::PossibleValue::new("windows")), Self::SunOs => Some(clap::builder::PossibleValue::new("sunos")), Self::Android => Some(clap::builder::PossibleValue::new("android")), Self::FreeBsd => Some(clap::builder::PossibleValue::new("freebsd")), Self::NetBsd => Some(clap::builder::PossibleValue::new("netbsd")), Self::OpenBsd => Some(clap::builder::PossibleValue::new("openbsd")), } } } impl PlatformType { #[cfg(target_os = "linux")] pub fn current() -> Self { Self::Linux } #[cfg(any(target_os = "macos", target_os = "dragonfly"))] pub fn current() -> Self { Self::OsX } #[cfg(target_os = "windows")] pub fn current() -> Self { Self::Windows } #[cfg(target_os = "android")] pub fn current() -> Self { Self::Android } #[cfg(target_os = "freebsd")] pub fn current() -> Self { Self::FreeBsd } #[cfg(target_os = "netbsd")] pub fn current() -> Self { Self::NetBsd } #[cfg(target_os = "openbsd")] pub fn current() -> Self { Self::OpenBsd } #[cfg(not(any( target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", target_os = "windows", target_os = "android", )))] pub fn current() -> Self { Self::Other } } #[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize, clap::ValueEnum)] #[serde(rename_all = "lowercase")] pub enum ColorOptions { Always, Auto, Never, } impl Default for ColorOptions { fn default() -> Self { Self::Auto } } #[derive(Debug, Eq, PartialEq)] pub enum LineType { Empty, Title(String), Description(String), ExampleText(String), ExampleCode(String), Other(String), } impl<'a> From<&'a str> for LineType { /// Convert a string slice to a `LineType`. Newlines and trailing whitespace are trimmed. fn from(line: &'a str) -> Self { let trimmed: &str = line.trim_end(); let mut chars = trimmed.chars(); match chars.next() { None => Self::Empty, Some('#') => Self::Title( trimmed .trim_start_matches(|chr: char| chr == '#' || chr.is_whitespace()) .into(), ), Some('>') => Self::Description( trimmed .trim_start_matches(|chr: char| chr == '>' || chr.is_whitespace()) .into(), ), Some(' ') => Self::ExampleCode(trimmed.trim_start_matches(char::is_whitespace).into()), Some(_) => Self::ExampleText(trimmed.into()), } } } impl LineType { /// Support for old format. /// TODO: Remove once old format has been phased out! pub fn from_v1(line: &str) -> Self { let trimmed = line.trim(); let mut chars = trimmed.chars(); match chars.next() { None => Self::Empty, Some('#') => Self::Title( trimmed .trim_start_matches(|chr: char| chr == '#' || chr.is_whitespace()) .into(), ), Some('>') => Self::Description( trimmed .trim_start_matches(|chr: char| chr == '>' || chr.is_whitespace()) .into(), ), Some('-') => Self::ExampleText( trimmed .trim_start_matches(|chr: char| chr == '-' || chr.is_whitespace()) .into(), ), Some('`') if chars.last() == Some('`') => Self::ExampleCode( trimmed .trim_matches(|chr: char| chr == '`' || chr.is_whitespace()) .into(), ), Some(_) => Self::Other(trimmed.into()), } } } /// The reason why a certain path (e.g. config path or cache dir) was chosen. #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum PathSource { /// OS convention (e.g. XDG on Linux) OsConvention, /// Env variable (TEALDEER_*) EnvVar, /// Config file ConfigFile, } impl fmt::Display for PathSource { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match self { Self::OsConvention => "OS convention", Self::EnvVar => "env variable", Self::ConfigFile => "config file", } ) } } #[cfg(test)] mod test { use super::LineType; #[test] fn test_linetype_from_str() { assert_eq!(LineType::from(""), LineType::Empty); assert_eq!(LineType::from(" \n \r"), LineType::Empty); assert_eq!( LineType::from("# Hello there"), LineType::Title("Hello there".into()) ); assert_eq!( LineType::from("> tis a description \n"), LineType::Description("tis a description".into()) ); assert_eq!( LineType::from("some command "), LineType::ExampleText("some command".into()) ); assert_eq!( LineType::from(" $ cargo run "), LineType::ExampleCode("$ cargo run".into()) ); } } tealdeer-1.7.0/src/utils.rs000064400000000000000000000013341046102023000137140ustar 00000000000000use yansi::Color; /// Print a warning to stderr. If `enable_styles` is true, then a yellow /// message will be printed. pub fn print_warning(enable_styles: bool, message: &str) { print_msg(enable_styles, message, "Warning: ", Color::Yellow); } /// Print an anyhow error to stderr. If `enable_styles` is true, then a red /// message will be printed. pub fn print_error(enable_styles: bool, error: &anyhow::Error) { print_msg(enable_styles, &format!("{error:?}"), "Error: ", Color::Red); } fn print_msg(enable_styles: bool, message: &str, prefix: &'static str, color: Color) { if enable_styles { eprintln!("{}{}", color.paint(prefix), color.paint(message)); } else { eprintln!("{message}"); } } tealdeer-1.7.0/tests/chmod.ru.expected000064400000000000000000000027211046102023000160240ustar 00000000000000 Изменить права доступа файлу или папке. Больше информации: . Дать [u]пользователю, который владеет файлом, права на его [x]исполнение:  chmod u+x файл Дать права [u]пользователю права [r]чтения и [w]записи в файл/папку:  chmod u+rw файл_или_папка Убрать права на [x]исполнение у [g]группы:  chmod g-x файл Дать [a]всем пользователям права на [r]чтение и [x]исполенеие:  chmod a+rx файл Дать [o]другим (не из группы владельцев файлом) такие же права как и у [g]группы:  chmod o=g файл Убрать все права у [o]других:  chmod o= файл Изменить права рекурсивно, дав [g]группе и [o]другим возможность [w]записи в папку:  chmod -R g+w,o+w папка tealdeer-1.7.0/tests/chmod.ru.md000064400000000000000000000022171046102023000146230ustar 00000000000000# chmod > Изменить права доступа файлу или папке. > Больше информации: . - Дать [u]пользователю, который владеет файлом, права на его [x]исполнение: `chmod u+x {{файл}}` - Дать права [u]пользователю права [r]чтения и [w]записи в файл/папку: `chmod u+rw {{файл_или_папка}}` - Убрать права на [x]исполнение у [g]группы: `chmod g-x {{файл}}` - Дать [a]всем пользователям права на [r]чтение и [x]исполенеие: `chmod a+rx {{файл}}` - Дать [o]другим (не из группы владельцев файлом) такие же права как и у [g]группы: `chmod o=g {{файл}}` - Убрать все права у [o]других: `chmod o= {{файл}}` - Изменить права рекурсивно, дав [g]группе и [o]другим возможность [w]записи в папку: `chmod -R g+w,o+w {{папка}}` tealdeer-1.7.0/tests/config.toml000064400000000000000000000006401046102023000147220ustar 00000000000000[style.highlight] foreground = "green" underline = false bold = false [style.command_name] bold = true [style.description] underline = false bold = false [style.example_text] foreground = "black" background = "blue" underline = false [style.example_variable] underline = true bold = false italic = true [display] use_pager = false compact = false [updates] auto_update = false auto_update_interval_hours = 720 tealdeer-1.7.0/tests/inkscape-default-no-color.expected000064400000000000000000000021611046102023000212500ustar 00000000000000 An SVG (Scalable Vector Graphics) editing program. Use -z to not open the GUI and only process files in the console. Open an SVG file in the Inkscape GUI: inkscape filename.svg Export an SVG file into a bitmap with the default format (PNG) and the default resolution (90 DPI): inkscape filename.svg -e filename.png Export an SVG file into a bitmap of 600x400 pixels (aspect ratio distortion may occur): inkscape filename.svg -e filename.png -w 600 -h 400 Export a single object, given its ID, into a bitmap: inkscape filename.svg -i id -e object.png Export an SVG document to PDF, converting all texts to paths: inkscape filename.svg | inkscape | inkscape --export-pdf=inkscape.pdf | inkscape | inkscape --export-text-to-path Duplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape: inkscape filename.svg --select=path123 --verb=EditDuplicate --verb=ObjectRotate90 --verb=FileSave --verb=FileQuit Some invalid command just to test the correct highlighting of the command name: inkscape --use-inkscape=v3.0 file tealdeer-1.7.0/tests/inkscape-default.expected000064400000000000000000000032251046102023000175240ustar 00000000000000 An SVG (Scalable Vector Graphics) editing program. Use -z to not open the GUI and only process files in the console. Open an SVG file in the Inkscape GUI:  inkscape filename.svg Export an SVG file into a bitmap with the default format (PNG) and the default resolution (90 DPI):  inkscape filename.svg -e filename.png Export an SVG file into a bitmap of 600x400 pixels (aspect ratio distortion may occur):  inkscape filename.svg -e filename.png -w 600 -h 400 Export a single object, given its ID, into a bitmap:  inkscape filename.svg -i id -e object.png Export an SVG document to PDF, converting all texts to paths:  inkscape filename.svg | inkscape | inkscape --export-pdf=inkscape.pdf | inkscape | inkscape --export-text-to-path Duplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape:  inkscape filename.svg --select=path123 --verb=EditDuplicate --verb=ObjectRotate90 --verb=FileSave --verb=FileQuit Some invalid command just to test the correct highlighting of the command name:  inkscape --use-inkscape=v3.0 file tealdeer-1.7.0/tests/inkscape-patched-no-color.expected000064400000000000000000000022451046102023000212370ustar 00000000000000 An SVG (Scalable Vector Graphics) editing program. Use -z to not open the GUI and only process files in the console. Open an SVG file in the Inkscape GUI: inkscape filename.svg Export an SVG file into a bitmap with the default format (PNG) and the default resolution (90 DPI): inkscape filename.svg -e filename.png Export an SVG file into a bitmap of 600x400 pixels (aspect ratio distortion may occur): inkscape filename.svg -e filename.png -w 600 -h 400 Export a single object, given its ID, into a bitmap: inkscape filename.svg -i id -e object.png Export an SVG document to PDF, converting all texts to paths: inkscape filename.svg | inkscape | inkscape --export-pdf=inkscape.pdf | inkscape | inkscape --export-text-to-path Duplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape: inkscape filename.svg --select=path123 --verb=EditDuplicate --verb=ObjectRotate90 --verb=FileSave --verb=FileQuit Some invalid command just to test the correct highlighting of the command name: inkscape --use-inkscape=v3.0 file Custom inkscape entry My Inkscape example tealdeer-1.7.0/tests/inkscape-v1.md000064400000000000000000000022231046102023000152220ustar 00000000000000# inkscape > An SVG (Scalable Vector Graphics) editing program. > Use -z to not open the GUI and only process files in the console. - Open an SVG file in the Inkscape GUI: `inkscape {{filename.svg}}` - Export an SVG file into a bitmap with the default format (PNG) and the default resolution (90 DPI): `inkscape {{filename.svg}} -e {{filename.png}}` - Export an SVG file into a bitmap of 600x400 pixels (aspect ratio distortion may occur): `inkscape {{filename.svg}} -e {{filename.png}} -w {{600}} -h {{400}}` - Export a single object, given its ID, into a bitmap: `inkscape {{filename.svg}} -i {{id}} -e {{object.png}}` - Export an SVG document to PDF, converting all texts to paths: `inkscape {{filename.svg}} | inkscape | inkscape --export-pdf={{inkscape.pdf}} | inkscape | inkscape --export-text-to-path` - Duplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape: `inkscape {{filename.svg}} --select=path123 --verb=EditDuplicate --verb=ObjectRotate90 --verb=FileSave --verb=FileQuit` - Some invalid command just to test the correct highlighting of the command name: `inkscape --use-inkscape=v3.0 file` tealdeer-1.7.0/tests/inkscape-v2.md000064400000000000000000000022321046102023000152230ustar 00000000000000inkscape ======== > An SVG (Scalable Vector Graphics) editing program. > Use -z to not open the GUI and only process files in the console. Open an SVG file in the Inkscape GUI: inkscape {{filename.svg}} Export an SVG file into a bitmap with the default format (PNG) and the default resolution (90 DPI): inkscape {{filename.svg}} -e {{filename.png}} Export an SVG file into a bitmap of 600x400 pixels (aspect ratio distortion may occur): inkscape {{filename.svg}} -e {{filename.png}} -w {{600}} -h {{400}} Export a single object, given its ID, into a bitmap: inkscape {{filename.svg}} -i {{id}} -e {{object.png}} Export an SVG document to PDF, converting all texts to paths: inkscape {{filename.svg}} | inkscape | inkscape --export-pdf={{inkscape.pdf}} | inkscape | inkscape --export-text-to-path Duplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape: inkscape {{filename.svg}} --select=path123 --verb=EditDuplicate --verb=ObjectRotate90 --verb=FileSave --verb=FileQuit Some invalid command just to test the correct highlighting of the command name: inkscape --use-inkscape=v3.0 file tealdeer-1.7.0/tests/inkscape-v2.patch.md000064400000000000000000000000571046102023000163240ustar 00000000000000Custom inkscape entry My Inkscape example tealdeer-1.7.0/tests/inkscape-with-config.expected000064400000000000000000000026371046102023000203240ustar 00000000000000 An SVG (Scalable Vector Graphics) editing program. Use -z to not open the GUI and only process files in the console. Open an SVG file in the Inkscape GUI: inkscape filename.svg Export an SVG file into a bitmap with the default format (PNG) and the default resolution (90 DPI): inkscape filename.svg -e filename.png Export an SVG file into a bitmap of 600x400 pixels (aspect ratio distortion may occur): inkscape filename.svg -e filename.png -w 600 -h 400 Export a single object, given its ID, into a bitmap: inkscape filename.svg -i id -e object.png Export an SVG document to PDF, converting all texts to paths: inkscape filename.svg | inkscape | inkscape --export-pdf=inkscape.pdf | inkscape | inkscape --export-text-to-path Duplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape: inkscape filename.svg --select=path123 --verb=EditDuplicate --verb=ObjectRotate90 --verb=FileSave --verb=FileQuit Some invalid command just to test the correct highlighting of the command name: inkscape --use-inkscape=v3.0 file tealdeer-1.7.0/tests/lib.rs000064400000000000000000000640251046102023000137030ustar 00000000000000//! Integration tests. use std::{ fs::{create_dir_all, File}, io::Write, process::Command, time::{Duration, SystemTime}, }; use assert_cmd::prelude::*; use predicates::{ boolean::PredicateBooleanExt, prelude::predicate::str::{contains, diff, is_empty, is_match}, }; use tempfile::{Builder as TempfileBuilder, TempDir}; // TODO: Should be 'cache::CACHE_DIR_ENV_VAR'. This requires to have a library crate for the logic. static CACHE_DIR_ENV_VAR: &str = "TEALDEER_CACHE_DIR"; pub static TLDR_PAGES_DIR: &str = "tldr-pages"; struct TestEnv { pub cache_dir: TempDir, pub custom_pages_dir: TempDir, pub config_dir: TempDir, pub input_dir: TempDir, pub default_features: bool, pub features: Vec, } impl TestEnv { fn new() -> Self { TestEnv { cache_dir: TempfileBuilder::new() .prefix(".tldr.test.cache") .tempdir() .unwrap(), config_dir: TempfileBuilder::new() .prefix(".tldr.test.conf") .tempdir() .unwrap(), custom_pages_dir: TempfileBuilder::new() .prefix(".tldr.test.custom-pages") .tempdir() .unwrap(), input_dir: TempfileBuilder::new() .prefix(".tldr.test.input") .tempdir() .unwrap(), default_features: true, features: vec![], } } /// Write `content` to "config.toml" in the `config_dir` directory fn write_config(&self, content: impl AsRef) { let config_file_name = self.config_dir.path().join("config.toml"); println!("Config path: {config_file_name:?}"); let mut config_file = File::create(&config_file_name).unwrap(); config_file.write_all(content.as_ref().as_bytes()).unwrap(); } /// Add entry for that environment to the "common" pages. fn add_entry(&self, name: &str, contents: &str) { self.add_os_entry("common", name, contents); } /// Add entry for that environment to an OS-specific subfolder. fn add_os_entry(&self, os: &str, name: &str, contents: &str) { let dir = self .cache_dir .path() .join(TLDR_PAGES_DIR) .join("pages") .join(os); create_dir_all(&dir).unwrap(); let mut file = File::create(dir.join(format!("{name}.md"))).unwrap(); file.write_all(contents.as_bytes()).unwrap(); } /// Add custom patch entry to the custom_pages_dir fn add_page_entry(&self, name: &str, contents: &str) { let dir = self.custom_pages_dir.path(); create_dir_all(dir).unwrap(); let mut file = File::create(dir.join(format!("{name}.page.md"))).unwrap(); file.write_all(contents.as_bytes()).unwrap(); } /// Add custom patch entry to the custom_pages_dir fn add_patch_entry(&self, name: &str, contents: &str) { let dir = self.custom_pages_dir.path(); create_dir_all(dir).unwrap(); let mut file = File::create(dir.join(format!("{name}.patch.md"))).unwrap(); file.write_all(contents.as_bytes()).unwrap(); } /// Disable default features. #[allow(dead_code)] // Might be useful in the future fn no_default_features(mut self) -> Self { self.default_features = false; self } /// Add the specified feature. #[allow(dead_code)] // Might be useful in the future fn with_feature>(mut self, feature: S) -> Self { self.features.push(feature.into()); self } /// Return a new `Command` with env vars set. fn command(&self) -> Command { let mut build = escargot::CargoBuild::new() .bin("tldr") .current_release() .current_target(); if !self.default_features { build = build.arg("--no-default-features"); } if !self.features.is_empty() { build = build.arg(format!("--feature {}", self.features.join(","))); } let run = build.run().unwrap(); let mut cmd = run.command(); cmd.env(CACHE_DIR_ENV_VAR, self.cache_dir.path().to_str().unwrap()); cmd.env( "TEALDEER_CONFIG_DIR", self.config_dir.path().to_str().unwrap(), ); cmd } } #[test] fn test_missing_cache() { TestEnv::new() .command() .args(["sl"]) .assert() .failure() .stderr(contains("Page cache not found. Please run `tldr --update`")); } #[test] fn test_update_cache() { let testenv = TestEnv::new(); testenv .command() .args(["sl"]) .assert() .failure() .stderr(contains("Page cache not found. Please run `tldr --update`")); testenv .command() .args(["--update"]) .assert() .success() .stderr(contains("Successfully updated cache.")); testenv.command().args(["sl"]).assert().success(); } #[test] fn test_quiet_cache() { let testenv = TestEnv::new(); testenv .command() .args(["--update", "--quiet"]) .assert() .success() .stdout(is_empty()); testenv .command() .args(["--clear-cache", "--quiet"]) .assert() .success() .stdout(is_empty()); } #[test] fn test_quiet_failures() { let testenv = TestEnv::new(); testenv .command() .args(["--update", "-q"]) .assert() .success() .stdout(is_empty()); testenv .command() .args(["fakeprogram", "-q"]) .assert() .failure() .stdout(is_empty()); } #[test] fn test_quiet_old_cache() { let testenv = TestEnv::new(); testenv .command() .args(["--update", "-q"]) .assert() .success() .stdout(is_empty()); filetime::set_file_mtime( testenv.cache_dir.path().join(TLDR_PAGES_DIR), filetime::FileTime::from_unix_time(1, 0), ) .unwrap(); testenv .command() .args(["tldr"]) .assert() .success() .stderr(contains("The cache hasn't been updated for ")); testenv .command() .args(["tldr", "--quiet"]) .assert() .success() .stderr(contains("The cache hasn't been updated for ").not()); } #[test] fn test_create_cache_directory_path() { let testenv = TestEnv::new(); let cache_dir = testenv.cache_dir.path(); let internal_cache_dir = cache_dir.join("internal"); let mut command = testenv.command(); command.env(CACHE_DIR_ENV_VAR, internal_cache_dir.to_str().unwrap()); assert!(!internal_cache_dir.exists()); command .arg("-u") .assert() .success() .stderr(contains(format!( "Successfully created cache directory path `{}`.", internal_cache_dir.to_str().unwrap() ))) .stderr(contains("Successfully updated cache.")); assert!(internal_cache_dir.is_dir()); } #[test] fn test_cache_location_not_a_directory() { let testenv = TestEnv::new(); let cache_dir = testenv.cache_dir.path(); let internal_file = cache_dir.join("internal"); File::create(&internal_file).unwrap(); let mut command = testenv.command(); command.env(CACHE_DIR_ENV_VAR, internal_file.to_str().unwrap()); command .arg("-u") .assert() .failure() .stderr(contains(format!( "Cache directory path `{}` is not a directory", internal_file.display(), ))) .stderr(contains( "Warning: The $TEALDEER_CACHE_DIR env variable is deprecated", )); } #[test] fn test_cache_location_source() { let testenv = TestEnv::new(); let default_cache_dir = testenv.cache_dir.path(); let tmp_cache_dir = TempfileBuilder::new() .prefix(".tldr.test.cache_dir") .tempdir() .unwrap(); // Source: Default (OS convention) let mut command = testenv.command(); command.env_remove(CACHE_DIR_ENV_VAR); command .arg("--show-paths") .assert() .success() .stdout(is_match("\nCache dir: [^(]* \\(OS convention\\)\n").unwrap()); // Source: Config variable let mut command = testenv.command(); command.env_remove(CACHE_DIR_ENV_VAR); testenv.write_config(format!( "[directories]\ncache_dir = '{}'", tmp_cache_dir.path().to_str().unwrap(), )); command .arg("--show-paths") .assert() .success() .stdout(is_match("\nCache dir: [^(]* \\(config file\\)\n").unwrap()); // Source: Env var let mut command = testenv.command(); command.env(CACHE_DIR_ENV_VAR, default_cache_dir.to_str().unwrap()); command .arg("--show-paths") .assert() .success() .stdout(is_match("\nCache dir: [^(]* \\(env variable\\)\n").unwrap()); } #[test] fn test_setup_seed_config() { let testenv = TestEnv::new(); testenv .command() .args(["--seed-config"]) .assert() .success() .stderr(contains("Successfully created seed config file here")); } #[test] fn test_show_paths() { let testenv = TestEnv::new(); // Show general commands testenv .command() .args(["--show-paths"]) .assert() .success() .stdout(contains(format!( "Config dir: {}", testenv.config_dir.path().to_str().unwrap(), ))) .stdout(contains(format!( "Config path: {}", testenv .config_dir .path() .join("config.toml") .to_str() .unwrap(), ))) .stdout(contains(format!( "Cache dir: {}", testenv.cache_dir.path().to_str().unwrap(), ))) .stdout(contains(format!( "Pages dir: {}", testenv .cache_dir .path() .join(TLDR_PAGES_DIR) .to_str() .unwrap(), ))); // Set custom pages directory testenv.write_config(format!( "[directories]\ncustom_pages_dir = '{}'", testenv.custom_pages_dir.path().to_str().unwrap() )); // Now ensure that this path is contained in the output testenv .command() .args(["--show-paths"]) .assert() .success() .stdout(contains(format!( "Custom pages dir: {}", testenv.custom_pages_dir.path().to_str().unwrap(), ))); } #[test] fn test_os_specific_page() { let testenv = TestEnv::new(); testenv.add_os_entry("sunos", "truss", "contents"); testenv .command() .args(["--platform", "sunos", "truss"]) .assert() .success(); } #[test] fn test_markdown_rendering() { let testenv = TestEnv::new(); testenv.add_entry("which", include_str!("which-markdown.expected")); let expected = include_str!("which-markdown.expected"); testenv .command() .args(["--raw", "which"]) .assert() .success() .stdout(diff(expected)); } fn _test_correct_rendering( input_file: &str, filename: &str, expected: &'static str, color_option: &str, ) { let testenv = TestEnv::new(); // Create input file let file_path = testenv.input_dir.path().join(filename); println!("Testfile path: {file_path:?}"); let mut file = File::create(&file_path).unwrap(); file.write_all(input_file.as_bytes()).unwrap(); testenv .command() .args(["--color", color_option, "-f", file_path.to_str().unwrap()]) .assert() .success() .stdout(diff(expected)); } /// An end-to-end integration test for direct file rendering (v1 syntax). #[test] fn test_correct_rendering_v1() { _test_correct_rendering( include_str!("inkscape-v1.md"), "inkscape-v1.md", include_str!("inkscape-default.expected"), "always", ); } /// An end-to-end integration test for direct file rendering (v2 syntax). #[test] fn test_correct_rendering_v2() { _test_correct_rendering( include_str!("inkscape-v2.md"), "inkscape-v2.md", include_str!("inkscape-default.expected"), "always", ); } #[test] /// An end-to-end integration test for direct file rendering with the `--color auto` option. This /// will not use styling since output is not stdout. fn test_rendering_color_auto() { _test_correct_rendering( include_str!("inkscape-v2.md"), "inkscape-v2.md", include_str!("inkscape-default-no-color.expected"), "auto", ); } #[test] /// An end-to-end integration test for direct file rendering with the `--color never` option. fn test_rendering_color_never() { _test_correct_rendering( include_str!("inkscape-v2.md"), "inkscape-v2.md", include_str!("inkscape-default-no-color.expected"), "never", ); } #[test] fn test_rendering_i18n() { _test_correct_rendering( include_str!("chmod.ru.md"), "chmod.ru.md", include_str!("chmod.ru.expected"), "always", ); } /// An end-to-end integration test for rendering with custom syntax config. #[test] fn test_correct_rendering_with_config() { let testenv = TestEnv::new(); // Setup config file // TODO should be config::CONFIG_FILE_NAME let config_file_path = testenv.config_dir.path().join("config.toml"); println!("Config path: {config_file_path:?}"); let mut config_file = File::create(&config_file_path).unwrap(); config_file .write_all(include_bytes!("config.toml")) .unwrap(); // Create input file let file_path = testenv.input_dir.path().join("inkscape-v2.md"); println!("Testfile path: {file_path:?}"); let mut file = File::create(&file_path).unwrap(); file.write_all(include_bytes!("inkscape-v2.md")).unwrap(); // Load expected output let expected = include_str!("inkscape-with-config.expected"); testenv .command() .args(["--color", "always", "-f", file_path.to_str().unwrap()]) .assert() .success() .stdout(diff(expected)); } #[test] fn test_spaces_find_command() { let testenv = TestEnv::new(); testenv .command() .args(["--update"]) .assert() .success() .stderr(contains("Successfully updated cache.")); testenv .command() .args(["git", "checkout"]) .assert() .success(); } #[test] fn test_pager_flag_enable() { let testenv = TestEnv::new(); testenv .command() .args(["--update"]) .assert() .success() .stderr(contains("Successfully updated cache.")); testenv .command() .args(["--pager", "which"]) .assert() .success(); } #[test] fn test_multiple_platform_command_search() { let testenv = TestEnv::new(); testenv.add_os_entry("linux", "linux-only", "this command only exists for linux"); testenv.add_os_entry( "linux", "windows-and-linux", "# windows-and-linux \n\n > linux version", ); testenv.add_os_entry( "windows", "windows-and-linux", "# windows-and-linux \n\n > windows version", ); testenv .command() .args(["--platform", "windows", "--platform", "linux", "linux-only"]) .assert() .success(); // test order of platforms supplied if preserved testenv .command() .args([ "--platform", "windows", "--platform", "linux", "windows-and-linux", ]) .assert() .success() .stdout(contains("windows version")); testenv .command() .args([ "--platform", "linux", "--platform", "windows", "windows-and-linux", ]) .assert() .success() .stdout(contains("linux version")); } #[test] fn test_multiple_platform_command_search_not_found() { let testenv = TestEnv::new(); testenv.add_os_entry( "windows", "windows-only", "this command only exists for Windows", ); testenv .command() .args(["--platform", "macos", "--platform", "linux", "windows-only"]) .assert() .stderr(contains("Page `windows-only` not found in cache.")); } #[test] fn test_list_flag_rendering() { let testenv = TestEnv::new(); // set custom pages directory testenv.write_config(format!( "[directories]\ncustom_pages_dir = '{}'", testenv.custom_pages_dir.path().to_str().unwrap() )); testenv .command() .args(["--list"]) .assert() .failure() .stderr(contains("Page cache not found. Please run `tldr --update`")); testenv.add_entry("foo", ""); testenv .command() .args(["--list"]) .assert() .success() .stdout("foo\n"); testenv.add_entry("bar", ""); testenv.add_entry("baz", ""); testenv.add_entry("qux", ""); testenv.add_page_entry("faz", ""); testenv.add_page_entry("bar", ""); testenv.add_page_entry("fiz", ""); testenv.add_patch_entry("buz", ""); testenv .command() .args(["--list"]) .assert() .success() .stdout("bar\nbaz\nfaz\nfiz\nfoo\nqux\n"); } #[test] fn test_multi_platform_list_flag_rendering() { let testenv = TestEnv::new(); // set custom pages directory testenv.write_config(format!( "[directories]\ncustom_pages_dir = '{}'", testenv.custom_pages_dir.path().to_str().unwrap() )); testenv.add_entry("common", ""); testenv .command() .args(["--list"]) .assert() .success() .stdout("common\n"); testenv .command() .args(["--platform", "linux", "--list"]) .assert() .success() .stdout("common\n"); testenv .command() .args(["--platform", "windows", "--list"]) .assert() .success() .stdout("common\n"); testenv.add_os_entry("linux", "rm", ""); testenv.add_os_entry("linux", "ls", ""); testenv.add_os_entry("windows", "del", ""); testenv.add_os_entry("windows", "dir", ""); testenv.add_os_entry("linux", "winux", ""); testenv.add_os_entry("windows", "winux", ""); // test `--list` for `--platform linux` by itself testenv .command() .args(["--platform", "linux", "--list"]) .assert() .success() .stdout("common\nls\nrm\nwinux\n"); // test `--list` for `--platform windows` by itself testenv .command() .args(["--platform", "windows", "--list"]) .assert() .success() .stdout("common\ndel\ndir\nwinux\n"); // test `--list` for `--platform linux --platform windows` testenv .command() .args(["--platform", "linux", "--platform", "windows", "--list"]) .assert() .success() .stdout("common\ndel\ndir\nls\nrm\nwinux\n"); // test `--list` for `--platform windows --platform linux` testenv .command() .args(["--platform", "linux", "--platform", "windows", "--list"]) .assert() .success() .stdout("common\ndel\ndir\nls\nrm\nwinux\n"); } #[test] fn test_autoupdate_cache() { let testenv = TestEnv::new(); // The first time, if automatic updates are disabled, the cache should not be found testenv .command() .args(["--list"]) .assert() .failure() .stderr(contains("Page cache not found. Please run `tldr --update`")); let config_file_path = testenv.config_dir.path().join("config.toml"); let cache_file_path = testenv.cache_dir.path().join(TLDR_PAGES_DIR); // Activate automatic updates, set the auto-update interval to 24 hours let mut config_file = File::create(config_file_path).unwrap(); config_file .write_all(b"[updates]\nauto_update = true\nauto_update_interval_hours = 24") .unwrap(); config_file.flush().unwrap(); // Helper function that runs `tldr --list` and asserts that the cache is automatically updated // or not, depending on the value of `expected`. let check_cache_updated = |expected| { let assert = testenv.command().args(["--list"]).assert().success(); let pred = contains("Successfully updated cache"); if expected { assert.stderr(pred) } else { assert.stderr(pred.not()) }; }; // The cache is updated the first time we run `tldr --list` check_cache_updated(true); // The cache is not updated with a subsequent call check_cache_updated(false); // We update the modification and access times such that they are about 23 hours from now. // auto-update interval is 24 hours, the cache should not be updated let new_mtime = SystemTime::now() - Duration::from_secs(82_800); filetime::set_file_mtime(&cache_file_path, new_mtime.into()).unwrap(); check_cache_updated(false); // We update the modification and access times such that they are about 25 hours from now. // auto-update interval is 24 hours, the cache should be updated let new_mtime = SystemTime::now() - Duration::from_secs(90_000); filetime::set_file_mtime(&cache_file_path, new_mtime.into()).unwrap(); check_cache_updated(true); // The cache is not updated with a subsequent call check_cache_updated(false); } /// End-end test to ensure .page.md files overwrite pages in cache_dir #[test] fn test_custom_page_overwrites() { let testenv = TestEnv::new(); // set custom pages directory testenv.write_config(format!( "[directories]\ncustom_pages_dir = '{}'", testenv.custom_pages_dir.path().to_str().unwrap() )); // Add file that should be ignored to the cache dir testenv.add_entry("inkscape-v2", ""); // Add .page.md file to custom_pages_dir testenv.add_page_entry("inkscape-v2", include_str!("inkscape-v2.md")); // Load expected output let expected = include_str!("inkscape-default-no-color.expected"); testenv .command() .args(["inkscape-v2", "--color", "never"]) .assert() .success() .stdout(diff(expected)); } /// End-End test to ensure that .patch.md files are appended to pages in the cache_dir #[test] fn test_custom_patch_appends_to_common() { let testenv = TestEnv::new(); // set custom pages directory testenv.write_config(format!( "[directories]\ncustom_pages_dir = '{}'", testenv.custom_pages_dir.path().to_str().unwrap() )); // Add page to the cache dir testenv.add_entry("inkscape-v2", include_str!("inkscape-v2.md")); // Add .page.md file to custom_pages_dir testenv.add_patch_entry("inkscape-v2", include_str!("inkscape-v2.patch.md")); // Load expected output let expected = include_str!("inkscape-patched-no-color.expected"); testenv .command() .args(["inkscape-v2", "--color", "never"]) .assert() .success() .stdout(diff(expected)); } /// End-End test to ensure that .patch.md files are not appended to .page.md files in the custom_pages_dir /// Maybe this interaction should change but I put this test here for the coverage #[test] fn test_custom_patch_does_not_append_to_custom() { let testenv = TestEnv::new(); // set custom pages directory testenv.write_config(format!( "[directories]\ncustom_pages_dir = '{}'", testenv.custom_pages_dir.path().to_str().unwrap() )); testenv.add_entry("test", ""); // Add page to the cache dir testenv.add_page_entry("inkscape-v2", include_str!("inkscape-v2.md")); // Add .page.md file to custom_pages_dir testenv.add_patch_entry("inkscape-v2", include_str!("inkscape-v2.patch.md")); // Load expected output let expected = include_str!("inkscape-default-no-color.expected"); testenv .command() .args(["inkscape-v2", "--color", "never"]) .assert() .success() .stdout(diff(expected)); } #[test] #[cfg(target_os = "windows")] fn test_pager_warning() { let testenv = TestEnv::new(); testenv .command() .args(["--update"]) .assert() .success() .stderr(contains("Successfully updated cache.")); // Regular call should not show a "pager flag not available on windows" warning testenv .command() .args(["which"]) .assert() .success() .stderr(contains("pager flag not available on Windows").not()); // But it should be shown if the pager flag is true testenv .command() .args(["--pager", "which"]) .assert() .success() .stderr(contains("pager flag not available on Windows")); } /// Ensure that page lookup is case insensitive, so a page lookup for `eyed3` /// and `eyeD3` should return the same page. #[test] fn test_lowercased_page_lookup() { let testenv = TestEnv::new(); // Lookup `eyed3`, initially fails testenv.command().args(["eyed3"]).assert().failure(); // Add entry testenv.add_entry("eyed3", "contents"); // Lookup `eyed3` again testenv.command().args(["eyed3"]).assert().success(); // Lookup `eyeD3`, should succeed as well testenv.command().args(["eyeD3"]).assert().success(); } /// Regression test for #219: It should be possible to combine `--raw` and `-f`. #[test] fn test_raw_render_file() { let testenv = TestEnv::new(); // Create input file let file_path = testenv.input_dir.path().join("inkscape.md"); let mut file = File::create(&file_path).unwrap(); file.write_all(include_bytes!("inkscape-v1.md")).unwrap(); // Base args let mut args = vec!["--color", "never", "-f", file_path.to_str().unwrap()]; // Default render testenv .command() .args(&args) .assert() .success() .stdout(diff(include_str!("inkscape-default-no-color.expected"))); // Raw render args.push("--raw"); testenv .command() .args(&args) .assert() .success() .stdout(diff(include_str!("inkscape-v1.md"))); } tealdeer-1.7.0/tests/which-markdown.expected000064400000000000000000000004001046102023000172170ustar 00000000000000# which > Locate a program in the user's path. - Search the PATH environment variable and display the location of any matching executables: `which {{executable}}` - If there are multiple executables which match, display all: `which -a {{executable}}`