shellexpand-3.1.0/.cargo_vcs_info.json0000644000000001360000000000100133440ustar { "git": { "sha1": "ce819863a2f9c9625d9c60d8fe803998d896e947" }, "path_in_vcs": "" }shellexpand-3.1.0/.github/workflows/main.yml000064400000000000000000000011701046102023000171770ustar 00000000000000# WARNING # # THIS PROJECT IS MAINTAINED IN GITLAB # So this file is not run and may be out of date. name: CI on: push: branches: [master] pull_request: branches: [master] jobs: test: runs-on: ubuntu-latest strategy: matrix: rust: [stable, beta, nightly] steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true - uses: actions-rs/cargo@v1 with: command: build - uses: actions-rs/cargo@v1 with: command: test shellexpand-3.1.0/.gitignore000064400000000000000000000000451046102023000141230ustar 00000000000000target Cargo.lock .*.sw? .idea *.iml shellexpand-3.1.0/.gitlab-ci.yml000064400000000000000000000035571046102023000146020ustar 00000000000000# Template originally came from: # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Rust.gitlab-ci.yml # Official language image. Look for the different tagged releases at: # https://hub.docker.com/r/library/rust/tags/ image: "rust:latest" stages: - test - comprehensive # Use cargo to test the project cargo-test: stage: test script: - rustc --version && cargo --version # Print version info for debugging - cp Cargo.lock.example Cargo.lock - cargo test --locked --workspace --verbose --all-features latest-deps: stage: comprehensive script: - rustc --version && cargo --version # Print version info for debugging - cp Cargo.lock.example Cargo.lock - cargo update - cargo test --locked --workspace --verbose --all-features feature-matrix: stage: comprehensive script: - cp Cargo.lock.example Cargo.lock - cargo test --locked --workspace --verbose - cargo test --locked --workspace --verbose --no-default-features --features=base-0 - cargo test --locked --workspace --verbose --no-default-features --features=base-0,path - cargo test --locked --workspace --verbose --no-default-features --features=base-0,tilde - cargo test --locked --workspace --verbose --no-default-features --features=full # Test our MSRV and Cargo.toml minimal versions: msrv-1.31: stage: comprehensive image: "rust:1.31.0" script: - mv Cargo.lock.minimal Cargo.lock - rustc --version && cargo --version # Print version info for debugging - cargo +1.31.0 test --locked --no-default-features --features=full-msrv-1-31 # Test our MSRV and Cargo.toml minimal versions msrv-1.51: stage: comprehensive image: "rust:1.51.0" script: - mv Cargo.lock.minimal Cargo.lock - rustc --version && cargo --version # Print version info for debugging - cargo +1.51.0 test --locked --workspace --all-features shellexpand-3.1.0/Cargo.lock.example000064400000000000000000000153641046102023000155040ustar 00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bstr" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" dependencies = [ "memchr", "once_cell", "regex-automata", "serde", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "dirs" version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" dependencies = [ "libc", "redox_users", "windows-sys", ] [[package]] name = "getrandom" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "libc" version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "once_cell" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "os_str_bytes" version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" dependencies = [ "memchr", ] [[package]] name = "proc-macro2" version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall", "thiserror", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "serde" version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" [[package]] name = "shellexpand" version = "3.1.0" dependencies = [ "bstr", "dirs", "os_str_bytes", ] [[package]] name = "syn" version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[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_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[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_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" shellexpand-3.1.0/Cargo.lock.minimal000064400000000000000000000151051046102023000154700ustar 00000000000000[[package]] name = "bitflags" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bstr" version = "1.0.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex-automata 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "byteorder" version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "dirs" version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dirs-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "dirs-sys" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", "redox_users 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "getrandom" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lazy_static" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" version = "0.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memchr" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "os_str_bytes" version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "redox_syscall" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "redox_users" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "getrandom 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-automata" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "shellexpand" version = "3.1.0" dependencies = [ "bstr 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "os_str_bytes 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum bstr 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c32c3b6bd6bb636665a8b1c91db8d115ad7345f7616541ced9ccf8d27efc55ec" "checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum dirs 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" "checksum dirs-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" "checksum getrandom 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)" = "74dfca3d9957906e8d1e6a0b641dc9a59848e793f1da2165889fd4f62d10d79c" "checksum memchr 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" "checksum os_str_bytes 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eec548a9ec896e20392939de0f32aabcdd8507358840f5ee4867792f3afcf494" "checksum redox_syscall 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bac5c4ce99d34f37ce30e45606946fcd6331223f1c98af4d29c5b6c4977d675b" "checksum redox_users 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" "checksum regex-automata 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4324c97bd13d1f83985e92e805e4b5ca6d058fcafb4ccfc5c0e388bebeadaafc" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum winapi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3ad91d846a4a5342c1fb7008d26124ee6cf94a3953751618577295373b32117" "checksum winapi-i686-pc-windows-gnu 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a16a8e2ebfc883e2b1771c6482b1fb3c6831eab289ba391619a2d93a7356220f" "checksum winapi-x86_64-pc-windows-gnu 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ca29cb03c8ceaf20f8224a18a530938305e9872b1478ea24ff44b4f503a1d1d" shellexpand-3.1.0/Cargo.toml0000644000000025210000000000100113420ustar # 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 = "2018" name = "shellexpand" version = "3.1.0" authors = [ "Vladimir Matveev ", "Ian Jackson ", ] description = "Shell-like expansions in strings" documentation = "http://docs.rs/shellexpand/" readme = "README.md" keywords = [ "strings", "shell", "variables", ] license = "MIT/Apache-2.0" repository = "https://gitlab.com/ijackson/rust-shellexpand" [lib] name = "shellexpand" [dependencies.bstr] version = "1.0.0-pre.2" optional = true [dependencies.dirs] version = ">=4, <6" optional = true [dependencies.os_str_bytes] version = ">=5, <7" optional = true [features] base-0 = [] default = [ "base-0", "tilde", ] full = ["full-msrv-1-51"] full-msrv-1-31 = [ "base-0", "tilde", ] full-msrv-1-51 = [ "full-msrv-1-31", "path", ] path = [ "bstr", "os_str_bytes", ] tilde = ["dirs"] shellexpand-3.1.0/Cargo.toml.orig000064400000000000000000000014601046102023000150240ustar 00000000000000[package] name = "shellexpand" version = "3.1.0" edition = "2018" authors = ["Vladimir Matveev ", "Ian Jackson "] license = "MIT/Apache-2.0" description = "Shell-like expansions in strings" repository = "https://gitlab.com/ijackson/rust-shellexpand" documentation = "http://docs.rs/shellexpand/" readme = "README.md" keywords = ["strings", "shell", "variables"] [dependencies] dirs = { version = ">=4, <6", optional = true } bstr = { version = "1.0.0-pre.2", optional = true } os_str_bytes = { version = ">=5, <7", optional = true } [features] default = ["base-0", "tilde"] full = ["full-msrv-1-51"] full-msrv-1-51 = ["full-msrv-1-31", "path"] full-msrv-1-31 = ["base-0", "tilde"] base-0 = [] path = ["bstr", "os_str_bytes" ] tilde = ["dirs"] [lib] name = "shellexpand" shellexpand-3.1.0/LICENSE-APACHE000064400000000000000000000251371046102023000140700ustar 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 APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. shellexpand-3.1.0/LICENSE-MIT000064400000000000000000000020731046102023000135720ustar 00000000000000The MIT License (MIT) Copyright (c) 2016 Vladimir Matveev 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. shellexpand-3.1.0/README.md000064400000000000000000000135251046102023000134210ustar 00000000000000shellexpand, a library for shell-like expansion in strings ========================================================== [![Build Status][actions]](https://gitlab.com/ijackson/rust-shellexpand/-/pipelines) [![crates.io][crates]](https://crates.io/crates/shellexpand) [![docs][docs]](https://docs.rs/shellexpand) [actions]: https://img.shields.io/gitlab/pipeline-status/ijackson/rust-shellexpand?branch=main&style=flat-square [crates]: https://img.shields.io/crates/v/shellexpand.svg?style=flat-square [docs]: https://img.shields.io/badge/docs-latest%20release-6495ed.svg?style=flat-square [Documentation](https://docs.rs/shellexpand/) shellexpand is a single dependency library which allows one to perform shell-like expansions in strings, that is, to expand variables like `$A` or `${B}` into their values inside some context and to expand `~` in the beginning of a string into the home directory (again, inside some context). This crate provides generic functions which accept arbitrary contexts as well as default, system-based functions which perform expansions using the system-wide context (represented by functions from `std::env` module and [dirs](https://crates.io/crates/dirs) crate). --- ### Alternatives to this crate: * [`expanduser`](https://docs.rs/expanduser/latest/expanduser/): Tilde substitution only. Supports `~user` which this crate currently does not (although we hope to). * [`envsubst`](https://docs.rs/envsubst/latest/envsubst/): Does not do offer tildeexpansion. Only supports certain concrete types (eg `HashMap` for variable map). * [`expand_str`](https://crates.io/crates/expand_str): Uses `%..%` syntax. Does not offer tilde expansion. Variable lookups can only be infallible. * [`tilde_expand`](https://crates.io/crates/tilde-expand): Only does tilde expansion, on bytes (`[u8]`). ## Usage Just add a dependency in your `Cargo.toml`: ```toml [dependencies] shellexpand = "3.0" ``` See the crate documentation (a link is present in the beginning of this readme) for more information and examples. ### Cargo features Functional features: * `tilde` (on by default): support for tilde expansion (home directory). * `path` (in `full`): support for operations on `Path`s. (MSRV: 1.51) Metafeatures: * `full`: all reasonable (non-experimental, non-hazardous) functionality. (Currently equivalent to `full-msrv-1.51`.) * `base-0` (on by default): basic functionality. You must enable this feature. * `full-msrv-1.51`: all reasonable functionality compatible with Rust 1.51. (currently: `base-0`, `tilde`, `paths`). * `full-msrv-1.31`: all reasonable functionality compatible with Rust 1.31. (currently: `base-0`, `tilde`). At the time of writing there is no experimental or hazardous functionality; if we introduce any it will be feature gated and not enabled by default nor part of `full*`. Requiring `base-0` allows us to split existing functionality into a new optional feature, without a semver break. We will try to avoid MSRV increases for existing functionality; increasing the MSRV for `full` will be minor version bump. ## Changelog ### Version 3.1.0 - 2023-03-24 Added: * cargo features `full-msrv-1.31` and `full-msrv-1.51` Fixed: * Explicitly declared MSRV 1.51 for `paths` feature. * Fixed build (without `paths` feature) with MSRV (1.31) Improved: * MSRV tested. * Update to `dirs` 5. (Allow use of dirs 4 too, since it's fine.) * Update `Cargo.lock.exaple` ### Version 3.0.0 - 2022-12-01 Breaking changes: * `tilde_with_context` and `full_with_context` now expect home directories as `str`, not `Path`. If you want to deal in Path, use the `path` feature and module. * You must select at least one cargo feature. The `base-0` feature is equivalent to the features available in shellexpand 2.x. Significant changes: * Use Rust 2018, bumping MSRV to 1.31.0. * New `path` module, and corresponding cargo feature. ### Version 2.1.2 Minor changes: * "Un-forked": now released as `shellexpand` on crates.io. * List alternatives to this crate. * Switch back to dirs from dirs-next. * Improve linking in docs and fix broken links and badges. * Apply some proposals from `cargo fix`. ### Version 2.1.1 * Fix tilde expanding on Windows with Windows style (backslash) paths. Addresses . * Forked as `shellexpand-fork` on crates.io. ### Version 2.1.0 * Switched to `dirs-next` instead of the obsolete `dirs` as the underlying dependency used to resolve the home directory * Switched to GitHub Actions instead of Travis CI for building the project. ### Version 2.0.0 * Added support for default values in variable expansion (i.e. `${ANSWER:-42}`) * Breaking changes (minimum Rust version is now 1.30.0): + Using `dyn` for trait objects to fix deprecation warning + Switched to using `source()` instead of `cause()` in the `Error` implementation, and therefore added a `'static` bound for the generic error parameter `E` ### Version 1.1.1 * Bump `dirs` dependency to 2.0. ### Version 1.1.0 * Changed use of deprecated `std::env::home_dir` to the [dirs](https://crates.io/crates/dirs)::home_dir function ### Version 1.0.0 * Fixed typos and minor incompletenesses in the documentation * Changed `home_dir` argument type for tilde expansion functions to `FnOnce` instead `FnMut` * Changed `LookupError::name` field name to `var_name` ### Version 0.1.0 * Initial release ## License This program is 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. shellexpand-3.1.0/maint/update-minimal-versions000075500000000000000000000014731046102023000177530ustar 00000000000000#!/bin/bash # # Update Cargo.lock.minimal. # # Run manually as needed. The Cargo.lock.minimal is used by # the minimal-versions test in .gitlab-ci.yml. # # Any arguments are passed through to "cargo update". set -euo pipefail # CARGO=nailing-cargo maint/update-minimal-versions # The -Z minimal-versions feature is only available in nightly # But we need an *old* nightly because we want to generate a Cargo.lock # that works with Rust 1.31. This one is roughy contemporaneous. : ${MINIMAL_NIGHTLY_RUST_VERSION=+nightly-2018-12-19} trap ' rc=$? mv Cargo.lock.example.aside Cargo.lock.example exit $rc ' 0 mv Cargo.lock.example Cargo.lock.example.aside # for nailing-cargo cp Cargo.lock.minimal Cargo.lock ${CARGO-cargo} $MINIMAL_NIGHTLY_RUST_VERSION update -Z minimal-versions "$@" mv Cargo.lock Cargo.lock.minimal shellexpand-3.1.0/src/funcs.rs000064400000000000000000000624671046102023000144260ustar 00000000000000//! Implementation - **instantiated twice** //! //! **IMPORTANT NOTE TO IMPLEMENTORS** //! //! This module is included twice - there are two `mod` statements. //! The `use super::wtraits::*` line imports *different types* each type. //! //! This allows the same code to do double duty: it works with `str`, and also with `Path`. //! Working with `Path` is quite awkward and complicated - see `path.rs` for the type definitions. //! //! The `wtraits` module has all the type names and traits we use, //! along with documentation of their semantics. //! //! But we also allow the use of inherent methods //! (if they do the right things with both the string and path types). use std::env::VarError; use std::error::Error; use super::wtraits::*; #[cfg(test)] mod test; /// Performs both tilde and environment expansion using the provided contexts. /// /// `home_dir` and `context` are contexts for tilde expansion and environment expansion, /// respectively. See [`env_with_context()`] and [`tilde_with_context()`] for more details on /// them. /// /// Unfortunately, expanding both `~` and `$VAR`s at the same time is not that simple. First, /// this function has to track ownership of the data. Since all functions in this crate /// return [`Cow`], this function takes some precautions in order not to allocate more than /// necessary. In particular, if the input string contains neither tilde nor `$`-vars, this /// function will perform no allocations. /// /// Second, if the input string starts with a variable, and the value of this variable starts /// with tilde, the naive approach may result into expansion of this tilde. This function /// avoids this. /// /// # Examples /// /// ``` /// use std::path::{PathBuf, Path}; /// use std::borrow::Cow; /// /// fn home_dir() -> Option { Some("/home/user".into()) } /// /// fn get_env(name: &str) -> Result, &'static str> { /// match name { /// "A" => Ok(Some("a value")), /// "B" => Ok(Some("b value")), /// "T" => Ok(Some("~")), /// "E" => Err("some error"), /// _ => Ok(None) /// } /// } /// /// // Performs both tilde and environment expansions /// assert_eq!( /// shellexpand::full_with_context("~/$A/$B", home_dir, get_env).unwrap(), /// "/home/user/a value/b value" /// ); /// /// // Errors from environment expansion are propagated to the result /// assert_eq!( /// shellexpand::full_with_context("~/$E/something", home_dir, get_env), /// Err(shellexpand::LookupError { /// var_name: "E".into(), /// cause: "some error" /// }) /// ); /// /// // Input without starting tilde and without variables does not cause allocations /// let s = shellexpand::full_with_context("some/path", home_dir, get_env); /// match s { /// Ok(Cow::Borrowed(s)) => assert_eq!(s, "some/path"), /// _ => unreachable!("the above variant is always valid") /// } /// /// // Input with a tilde inside a variable in the beginning of the string does not cause tilde /// // expansion /// assert_eq!( /// shellexpand::full_with_context("$T/$A/$B", home_dir, get_env).unwrap(), /// "~/a value/b value" /// ); /// ``` pub fn full_with_context( input: &SI, home_dir: HD, context: C, ) -> Result, LookupError> where SI: AsRef, CO: AsRef, C: FnMut(&str) -> Result, E>, P: AsRef, HD: FnOnce() -> Option

, { env_with_context(input, context).map(|r| match r { // variable expansion did not modify the original string, so we can apply tilde expansion // directly Cow::Borrowed(s) => tilde_with_context(s, home_dir), Cow::Owned(s) => { // if the original string does not start with a tilde but the processed one does, // then the tilde is contained in one of variables and should not be expanded // (We must convert the input to WInput here because it might be `AsRef`. // and `Path`'s `starts_with` checks only whole components; // and `OsStr` doesn't let us test prefixes at all.) if !input.into_winput().starts_with('~') && s.starts_with("~") { // return as is s.into() } else if let Cow::Owned(s) = tilde_with_context(&s, home_dir) { s.into() } else { s.into() } } }) } /// Same as [`full_with_context()`], but forbids the variable lookup function to return errors. /// /// This function also performs full shell-like expansion, but it uses /// [`env_with_context_no_errors()`] for environment expansion whose context lookup function returns /// just [`Option`] instead of [`Result, E>`]. Therefore, the function itself also /// returns just [`Cow`] instead of [`Result, LookupError>`]. Otherwise it is /// identical to [`full_with_context()`]. /// /// # Examples /// /// ``` /// use std::path::{PathBuf, Path}; /// use std::borrow::Cow; /// /// fn home_dir() -> Option { Some("/home/user".into()) } /// /// fn get_env(name: &str) -> Option<&'static str> { /// match name { /// "A" => Some("a value"), /// "B" => Some("b value"), /// "T" => Some("~"), /// _ => None /// } /// } /// /// // Performs both tilde and environment expansions /// assert_eq!( /// shellexpand::full_with_context_no_errors("~/$A/$B", home_dir, get_env), /// "/home/user/a value/b value" /// ); /// /// // Input without starting tilde and without variables does not cause allocations /// let s = shellexpand::full_with_context_no_errors("some/path", home_dir, get_env); /// match s { /// Cow::Borrowed(s) => assert_eq!(s, "some/path"), /// _ => unreachable!("the above variant is always valid") /// } /// /// // Input with a tilde inside a variable in the beginning of the string does not cause tilde /// // expansion /// assert_eq!( /// shellexpand::full_with_context_no_errors("$T/$A/$B", home_dir, get_env), /// "~/a value/b value" /// ); /// ``` #[inline] pub fn full_with_context_no_errors( input: &SI, home_dir: HD, mut context: C, ) -> Cow where SI: AsRef, CO: AsRef, C: FnMut(&str) -> Option, P: AsRef, HD: FnOnce() -> Option

, { match full_with_context(input, home_dir, move |s| Ok::, ()>(context(s))) { Ok(result) => result, Err(_) => unreachable!(), } } /// Performs both tilde and environment expansions in the default system context. /// /// This function delegates to [`full_with_context()`], using the default system sources for both /// home directory and environment, namely [`dirs::home_dir()`] and [`std::env::var()`]. /// /// Note that variable lookup of unknown variables will fail with an error instead of, for example, /// replacing the unknown variable with an empty string. The author thinks that this behavior is /// more useful than the other ones. If you need to change it, use [`full_with_context()`] or /// [`full_with_context_no_errors()`] with an appropriate context function instead. /// /// This function behaves exactly like [`full_with_context()`] in regard to tilde-containing /// variables in the beginning of the input string. /// /// # Examples /// /// ``` /// use std::env; /// /// env::set_var("A", "a value"); /// env::set_var("B", "b value"); /// /// let home_dir = dirs::home_dir() /// .map(|p| p.display().to_string()) /// .unwrap_or_else(|| "~".to_owned()); /// /// // Performs both tilde and environment expansions using the system contexts /// assert_eq!( /// shellexpand::full("~/$A/${B}s").unwrap(), /// format!("{}/a value/b values", home_dir) /// ); /// /// // Unknown variables cause expansion errors /// assert_eq!( /// shellexpand::full("~/$UNKNOWN/$B"), /// Err(shellexpand::LookupError { /// var_name: "UNKNOWN".into(), /// cause: env::VarError::NotPresent /// }) /// ); /// ``` #[cfg(feature = "tilde")] #[inline] pub fn full(input: &SI) -> Result, LookupError> where SI: AsRef, { full_with_context(input, home_dir, |s| std::env::var(s).map(Some)) } #[cfg(feature = "tilde")] fn home_dir() -> Option { let hd = dirs::home_dir()?; // If the home directory is not valid Unicode, we might not be able to substitute it. // We don't have an error reporting channel suitable for this (very unusual) situation. // If it happens, we just return `None`, causing `~`-substitution to not occur. // // In `shellexpand` 2.x, we use `Path::display()`, instead, which is lossy - so we would // use a wrong pathname. That is definitely worse. hd.try_into_xstring() } /// Represents a variable lookup error. /// /// This error is returned by [`env_with_context()`] function (and, therefore, also by [`env()`], /// [`full_with_context()`] and [`full()`]) when the provided context function returns an error. The /// original error is provided in the `cause` field, while `name` contains the name of a variable /// whose expansion caused the error. #[derive(Debug, Clone, PartialEq, Eq)] pub struct LookupError { /// The name of the problematic variable inside the input string. pub var_name: OString, /// The original error returned by the context function. pub cause: E, } impl fmt::Display for LookupError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "error looking key '")?; self.var_name.display_possibly_lossy(f)?; write!(f, "' up: {}", self.cause)?; Ok(()) } } impl Error for LookupError { fn source(&self) -> Option<&(dyn Error + 'static)> { Some(&self.cause) } } macro_rules! try_lookup { ($name:expr, $e:expr) => { match $e { Ok(s) => s, Err(e) => { return Err(LookupError { var_name: $name.to_ostring(), cause: e, }) } } }; } fn is_valid_var_name_char(c: char) -> bool { c.is_alphanumeric() || c == '_' } /// Performs the environment expansion using the provided context. /// /// This function walks through the input string `input` and attempts to construct a new string by /// replacing all shell-like variable sequences with the corresponding values obtained via the /// `context` function. The latter may return an error; in this case the error will be returned /// immediately, along with the name of the offending variable. Also the context function may /// return `Ok(None)`, indicating that the given variable is not available; in this case the /// variable sequence is left as it is in the output string. /// /// The syntax of variables resembles the one of bash-like shells: all of `$VAR`, `${VAR}`, /// `$NAME_WITH_UNDERSCORES` are valid variable references, and the form with braces may be used to /// separate the reference from the surrounding alphanumeric text: `before${VAR}after`. Note, /// however, that for simplicity names like `$123` or `$1AB` are also valid, as opposed to shells /// where `$` has special meaning of positional arguments. Also note that "alphanumericity" /// of variable names is checked with [`std::primitive::char::is_alphanumeric()`], therefore lots of characters which /// are considered alphanumeric by the Unicode standard are also valid names for variables. When /// unsure, use braces to separate variables from the surrounding text. /// /// This function has four generic type parameters: `SI` represents the input string, `CO` is the /// output of context lookups, `C` is the context closure and `E` is the type of errors returned by /// the context function. `SI` and `CO` must be types, a references to which can be converted to /// a string slice. For example, it is fine for the context function to return [`&str`]'s, [`String`]'s or /// [`Cow`]'s, which gives the user a lot of flexibility. /// /// If the context function returns an error, it will be wrapped into [`LookupError`] and returned /// immediately. [`LookupError`], besides the original error, also contains a string with the name of /// the variable whose expansion caused the error. [`LookupError`] implements [`Error`], [`Clone`] and /// [`Eq`] traits for further convenience and interoperability. /// /// If you need to expand system environment variables, you can use [`env()`] or [`full()`] functions. /// If your context does not have errors, you may use [`env_with_context_no_errors()`] instead of /// this function because it provides a simpler API. /// /// # Examples /// /// ``` /// fn context(s: &str) -> Result, &'static str> { /// match s { /// "A" => Ok(Some("a value")), /// "B" => Ok(Some("b value")), /// "E" => Err("something went wrong"), /// _ => Ok(None) /// } /// } /// /// // Regular variables are expanded /// assert_eq!( /// shellexpand::env_with_context("begin/$A/${B}s/end", context).unwrap(), /// "begin/a value/b values/end" /// ); /// /// // Expand to a default value if the variable is not defined /// assert_eq!( /// shellexpand::env_with_context("begin/${UNSET_ENV:-42}/end", context).unwrap(), /// "begin/42/end" /// ); /// /// // Unknown variables are left as is /// assert_eq!( /// shellexpand::env_with_context("begin/$UNKNOWN/end", context).unwrap(), /// "begin/$UNKNOWN/end" /// ); /// /// // Errors are propagated /// assert_eq!( /// shellexpand::env_with_context("begin${E}end", context), /// Err(shellexpand::LookupError { /// var_name: "E".into(), /// cause: "something went wrong" /// }) /// ); /// ``` pub fn env_with_context( input: &SI, mut context: C, ) -> Result, LookupError> where SI: AsRef, CO: AsRef, C: FnMut(&str) -> Result, E>, { let input_str = input.into_winput(); if let Some(idx) = input_str.find('$') { let mut result = OString::with_capacity(input_str.len()); let mut input_str = input_str.as_wstr(); let mut next_dollar_idx = idx; loop { result.push_wstr(&input_str[..next_dollar_idx]); input_str = &input_str[next_dollar_idx..]; if input_str.is_empty() { break; } fn find_dollar(s: &Wstr) -> usize { s.find('$').unwrap_or(s.len()) } let mut lookup = |var_name: &Wstr| { let var_name = match var_name.as_str() { Some(var_name) => var_name, // No non-UTF-8 variables can exist None => return Ok(None), }; context(var_name) }; let mut next_chars = input_str[1..].chars_approx(); let next_char = next_chars.next(); if next_char == Some('{') { match input_str.find('}') { Some(closing_brace_idx) => { let mut default_value = None; // Search for the default split let var_name_end_idx = match input_str[..closing_brace_idx].find(":-") { // Only match if there's a variable name, ie. this is not valid ${:-value} Some(default_split_idx) if default_split_idx != 2 => { default_value = Some(&input_str[default_split_idx + 2..closing_brace_idx]); default_split_idx } _ => closing_brace_idx, }; let var_name = &input_str[2..var_name_end_idx]; match lookup(var_name) { // if we have the variable set to some value Ok(Some(var_value)) => { result.push_xstr(var_value.as_ref()); input_str = &input_str[closing_brace_idx + 1..]; next_dollar_idx = find_dollar(input_str); } // if the variable is set and empty or unset not_found_or_empty => { let value = match (not_found_or_empty, default_value) { // return an error if we don't have a default and the variable is unset (Err(err), None) => { return Err(LookupError { var_name: var_name.to_ostring(), cause: err, }); } // use the default value if set (_, Some(default)) => default, // leave the variable as it is if the environment is empty (_, None) => &input_str[..closing_brace_idx + 1], }; result.push_wstr(value); input_str = &input_str[closing_brace_idx + 1..]; next_dollar_idx = find_dollar(input_str); } } } // unbalanced braces None => { result.push_wstr(&input_str[..2]); input_str = &input_str[2..]; next_dollar_idx = find_dollar(input_str); } } } else if next_char.map(is_valid_var_name_char) == Some(true) { let mut end_idx; loop { // Subtract the bytes length of the remainder from the length, and that's where we are end_idx = input_str.len() - next_chars.wstr_len(); match next_chars.next() { Some(c) if is_valid_var_name_char(c) => {}, _ => break, } } let var_name = &input_str[1..end_idx]; match try_lookup!(var_name, lookup(var_name)) { Some(var_value) => { result.push_xstr(var_value.as_ref()); input_str = &input_str[end_idx..]; next_dollar_idx = find_dollar(input_str); } None => { result.push_wstr(&input_str[..end_idx]); input_str = &input_str[end_idx..]; next_dollar_idx = find_dollar(input_str); } } } else { result.push_str("$"); input_str = if next_char == Some('$') { &input_str[2..] // skip the next dollar for escaping } else { &input_str[1..] }; next_dollar_idx = find_dollar(input_str); }; } Ok(result.into_ocow()) } else { Ok(input.into_ocow()) } } /// Same as [`env_with_context()`], but forbids the variable lookup function to return errors. /// /// This function also performs environment expansion, but it requires context function of type /// `FnMut(&str) -> Option` instead of `FnMut(&str) -> Result, E>`. This simplifies /// the API when you know in advance that the context lookups may not fail. /// /// Because of the above, instead of [`Result, LookupError>`] this function returns just /// [`Cow`]. /// /// Note that if the context function returns [`None`], the behavior remains the same as that of /// [`env_with_context()`]: the variable reference will remain in the output string unexpanded. /// /// # Examples /// /// ``` /// fn context(s: &str) -> Option<&'static str> { /// match s { /// "A" => Some("a value"), /// "B" => Some("b value"), /// _ => None /// } /// } /// /// // Known variables are expanded /// assert_eq!( /// shellexpand::env_with_context_no_errors("begin/$A/${B}s/end", context), /// "begin/a value/b values/end" /// ); /// /// // Unknown variables are left as is /// assert_eq!( /// shellexpand::env_with_context_no_errors("begin/$U/end", context), /// "begin/$U/end" /// ); /// ``` #[inline] pub fn env_with_context_no_errors(input: &SI, mut context: C) -> Cow where SI: AsRef, CO: AsRef, C: FnMut(&str) -> Option, { match env_with_context(input, move |s| Ok::, ()>(context(s))) { Ok(value) => value, Err(_) => unreachable!(), } } /// Performs the environment expansion using the default system context. /// /// This function delegates to [`env_with_context()`], using the default system source for /// environment variables, namely the [`std::env::var()`] function. /// /// Note that variable lookup of unknown variables will fail with an error instead of, for example, /// replacing the offending variables with an empty string. The author thinks that such behavior is /// more useful than the other ones. If you need something else, use [`env_with_context()`] or /// [`env_with_context_no_errors()`] with an appropriate context function. /// /// # Examples /// /// ``` /// use std::env; /// /// // make sure that some environment variables are set /// env::set_var("X", "x value"); /// env::set_var("Y", "y value"); /// /// // Known variables are expanded /// assert_eq!( /// shellexpand::env("begin/$X/${Y}s/end").unwrap(), /// "begin/x value/y values/end" /// ); /// /// // Unknown variables result in an error /// assert_eq!( /// shellexpand::env("begin/$Z/end"), /// Err(shellexpand::LookupError { /// var_name: "Z".into(), /// cause: env::VarError::NotPresent /// }) /// ); /// ``` #[inline] pub fn env(input: &SI) -> Result, LookupError> where SI: AsRef, { env_with_context(input, |s| std::env::var(s).map(Some)) } /// Performs the tilde expansion using the provided context. /// /// This function expands tilde (`~`) character in the beginning of the input string into contents /// of the path returned by `home_dir` function. If the input string does not contain a tilde, or /// if it is not followed either by a slash (`/`) or by the end of string, then it is also left as /// is. This means, in particular, that expansions like `~anotheruser/directory` are not supported. /// The context function may also return a `None`, in that case even if the tilde is present in the /// input in the correct place, it won't be replaced (there is nothing to replace it with, after /// all). /// /// This function has three generic type parameters: `SI` represents the input string, `P` is the /// output of a context lookup, and `HD` is the context closure. `SI` must be a type, a reference /// to which can be converted to a string slice via [`AsRef`], and `P` must be a type, a /// reference to which can be converted to a `str` via [`AsRef`]. /// Home directories which are available only as a `Path` are not supported here, /// because they cannot be represented in the output string. /// If you wish to support home directories which are not valid Unicode, /// use the [`path`](crate::path) module. /// /// If you need to expand the tilde into the actual user home directory, you can use [`tilde()`] or /// [`full()`] functions. /// /// # Examples /// /// ``` /// use std::path::{PathBuf, Path}; /// /// fn home_dir() -> Option { Some("/home/user".into()) } /// /// assert_eq!( /// shellexpand::tilde_with_context("~/some/dir", home_dir), /// "/home/user/some/dir" /// ); /// ``` pub fn tilde_with_context(input: &SI, home_dir: HD) -> Cow where SI: AsRef, P: AsRef, HD: FnOnce() -> Option

, { let input_str = input.into_winput(); if let Some(input_after_tilde) = input_str.strip_prefix('~') { if input_after_tilde.is_empty() || input_after_tilde.starts_with('/') || (cfg!(windows) && input_after_tilde.starts_with('\\')) { if let Some(hd) = home_dir() { let hd = hd.into_winput(); let mut result = OString::with_capacity(hd.len() + input_after_tilde.len()); result.push_wstr(hd.as_wstr()); result.push_wstr(input_after_tilde); result.into_ocow() } else { // home dir is not available input.into_ocow() } } else { // we cannot handle `~otheruser/` paths yet input.into_ocow() } } else { // input doesn't start with tilde input.into_ocow() } } /// Performs the tilde expansion using the default system context. /// /// This function delegates to [`tilde_with_context()`], using the default system source of home /// directory path, namely [`dirs::home_dir()`] function. /// /// # Examples /// /// ``` /// let hds = dirs::home_dir() /// .map(|p| p.display().to_string()) /// .unwrap_or_else(|| "~".to_owned()); /// /// assert_eq!( /// shellexpand::tilde("~/some/dir"), /// format!("{}/some/dir", hds) /// ); /// ``` #[cfg(feature = "tilde")] #[inline] pub fn tilde(input: &SI) -> Cow where SI: AsRef, { tilde_with_context(input, home_dir) } shellexpand-3.1.0/src/lib.rs000064400000000000000000000100451046102023000140370ustar 00000000000000//! Provides functions for performing shell-like expansions in strings. //! //! In particular, the following expansions are supported: //! //! * tilde expansion, when `~` in the beginning of a string, like in `"~/some/path"`, //! is expanded into the home directory of the current user; //! * environment expansion, when `$A` or `${B}`, like in `"~/$A/${B}something"`, //! are expanded into their values in some environment. //! //! Environment expansion also supports default values with the familiar shell syntax, //! so for example `${UNSET_ENV:-42}` will use the specified default value, i.e. `42`, if //! the `UNSET_ENV` variable is not set in the environment. //! //! The source of external information for these expansions (home directory and environment //! variables) is called their *context*. The context is provided to these functions as a closure //! of the respective type. //! //! This crate provides both customizable functions, which require their context to be provided //! explicitly, and wrapper functions which use [`dirs::home_dir()`] and [`std::env::var()`] //! for obtaining home directory and environment variables, respectively. //! //! Also there is a "full" function which performs both tilde and environment //! expansion, but does it correctly, rather than just doing one after another: for example, //! if the string starts with a variable whose value starts with a `~`, then this tilde //! won't be expanded. //! //! All functions return [`Cow`][Cow] because it is possible for their input not to contain anything //! which triggers the expansion. In that case performing allocations can be avoided. //! //! Please note that by default unknown variables in environment expansion are left as they are //! and are not, for example, substituted with an empty string: //! //! ``` //! fn context(_: &str) -> Option { None } //! //! assert_eq!( //! shellexpand::env_with_context_no_errors("$A $B", context), //! "$A $B" //! ); //! ``` //! //! Environment expansion context allows for a very fine tweaking of how results should be handled, //! so it is up to the user to pass a context function which does the necessary thing. For example, //! [`env()`] and [`full()`] functions from this library pass all errors returned by [`std::env::var()`] //! through, therefore they will also return an error if some unknown environment //! variable is used, because [`std::env::var()`] returns an error in this case: //! //! ``` //! use std::env; //! //! // make sure that the variable indeed does not exist //! env::remove_var("MOST_LIKELY_NONEXISTING_VAR"); //! //! assert_eq!( //! shellexpand::env("$MOST_LIKELY_NONEXISTING_VAR"), //! Err(shellexpand::LookupError { //! var_name: "MOST_LIKELY_NONEXISTING_VAR".into(), //! cause: env::VarError::NotPresent //! }) //! ); //! ``` //! //! The author thinks that this approach is more useful than just substituting an empty string //! (like, for example, does Go with its [os.ExpandEnv](https://golang.org/pkg/os/#ExpandEnv) //! function), but if you do need `os.ExpandEnv`-like behavior, it is fairly easy to get one: //! //! ``` //! use std::env; //! use std::borrow::Cow; //! //! fn context(s: &str) -> Result>, env::VarError> { //! match env::var(s) { //! Ok(value) => Ok(Some(value.into())), //! Err(env::VarError::NotPresent) => Ok(Some("".into())), //! Err(e) => Err(e) //! } //! } //! //! // make sure that the variable indeed does not exist //! env::remove_var("MOST_LIKELY_NONEXISTING_VAR"); //! //! assert_eq!( //! shellexpand::env_with_context("a${MOST_LIKELY_NOEXISTING_VAR}b", context).unwrap(), //! "ab" //! ); //! ``` //! //! The above example also demonstrates the flexibility of context function signatures: the context //! function may return anything which can be `AsRef`ed into a string slice. //! //! [Cow]: std::borrow::Cow mod strings; pub use self::strings::funcs::*; #[cfg(feature = "path")] pub mod path; #[cfg(not(feature = "base-0"))] compile_error!("You must enable the base-0 feature. See the crate-level README."); shellexpand-3.1.0/src/path.rs000064400000000000000000000052771046102023000142400ustar 00000000000000//! Expansion of [`Path`] values //! //! These functions are the same as the ones in the crate toplevel, //! except that they take [`Path`]s as input and return `Cow`. //! //! (Note that the individual doc comments and examples still refer to //! `str` and `String` and so on - please refer to the actual types. //! The semantics are as described.) use std::ffi::OsString; use std::path::Path; use bstr::ByteSlice as _; #[path="wtraits.rs"] pub(crate) mod wtraits; use wtraits::*; #[path="funcs.rs"] pub(crate) mod funcs; pub use funcs::*; use os_str_bytes::RawOsStr; type Xstr = std::path::Path; pub(crate) type WInput<'x> = Cow<'x, RawOsStr>; pub(crate) type Wstr = RawOsStr; pub(crate) type OString = OsString; impl XstrRequirements for Path { } impl + ?Sized> AsRefXstrExt for P { fn into_winput(&self) -> Cow<'_, RawOsStr> { RawOsStr::new(self.as_ref().as_os_str()) } fn into_ocow(&self) -> Cow<'_, Path> { self.as_ref().into() } } impl WstrExt for RawOsStr { fn as_str(&self) -> Option<&str> { self.to_str() } fn len(&self) -> usize { self.raw_len() } fn to_ostring(&self) -> OsString { self.to_os_str().into_owned() } fn strip_prefix(&self, c: char) -> Option<&Self> { self.strip_prefix(c) } } impl<'s> WstrRefExt for &'s RawOsStr { type Chars = bstr::Chars<'s>; /// This is quite approximate, really. /// /// os_str_bytes says the encoding is "compatible with" UTF-8, and that splitting on UTF-8 /// characters yields valid substrings. /// /// We assume, without justification, that the characters we care about handling correctly /// in un-{} $var expansions, are represented closely enough that this works. /// /// On Unix-using-UTF-8 this will be true because it's all, in fact, Unicode. /// On other Unix, at least ASCII will work right. /// On Windows things that use surrogates will possibly go wrong? /// On platforms where this is some mutant form of EBCDIC or something, this will be hopeless. fn chars_approx(self) -> bstr::Chars<'s> { self.as_raw_bytes().chars() } } impl CharsExt for bstr::Chars<'_> { fn wstr_len(&self) -> usize { self.as_bytes().len() } } impl OStringExt for OsString { fn push_str(&mut self, x: &str) { self.push(x) } fn push_wstr(&mut self, x: &RawOsStr) { self.push(x.to_os_str()) } fn push_xstr(&mut self, x: &Path) { self.push(x.as_os_str()) } fn into_ocow(self) -> Cow<'static, Path> { PathBuf::from(self).into() } fn display_possibly_lossy(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.to_string_lossy()) } } impl PathBufExt for PathBuf { fn try_into_xstring(self) -> Option { Some(self) } } shellexpand-3.1.0/src/strings.rs000064400000000000000000000033021046102023000147600ustar 00000000000000 use std::fmt::Display; use std::str::Chars; #[path="wtraits.rs"] pub mod wtraits; use self::wtraits::*; #[path="funcs.rs"] pub mod funcs; pub type Xstr = str; pub type WInput<'x> = &'x str; pub type Wstr = str; pub type OString = String; impl XstrRequirements for str { } impl + ?Sized> AsRefXstrExt for S { fn into_winput(&self) -> &str { self.as_ref() } fn into_ocow(&self) -> Cow<'_, str> { self.as_ref().into() } } impl WstrExt for str { fn as_str(&self) -> Option<&str> { Some(self) } fn len(&self) -> usize { str::len(self) } fn to_ostring(&self) -> String { self.to_owned() } fn strip_prefix(&self, c: char) -> Option<&Self> { if self.starts_with(c) { Some(&self[c.len_utf8()..]) } else { None } } } impl<'s> WstrRefExt for &'s str { type Chars = Chars<'s>; /// Must be used only for the-{}-unbracketed $varname expansion variable name termination detection /// /// The implementation for `paths.rs` is ... limited. fn chars_approx(self) -> Chars<'s> { self.chars() } } impl CharsExt for Chars<'_> { fn wstr_len(&self) -> usize { self.as_str().len() } } impl OStringExt for String { fn push_str(&mut self, x: &str) { self.push_str(x) } fn push_wstr(&mut self, x: &str) { self.push_str(x) } fn push_xstr(&mut self, x: &str) { self.push_str(x) } fn into_ocow(self) -> Cow<'static, str> { self.into() } fn display_possibly_lossy(&self, f: &mut fmt::Formatter) -> fmt::Result { Display::fmt(self, f) } } impl PathBufExt for PathBuf { fn try_into_xstring(self) -> Option { self.into_os_string().into_string().ok() } } shellexpand-3.1.0/src/test.rs000064400000000000000000000235041046102023000142540ustar 00000000000000 use super::*; /// Convert a `str` to an `Xstr`, for use with expected results in test cases fn s(s: &str) -> &Xstr { s.as_ref() } /// Convert a `str` to a `OString`, for use with expected variable lookup errors fn mk_var_name(vn: &str) -> OString { s(vn).into_winput().as_wstr().to_ostring() } #[cfg(feature = "tilde")] mod tilde_tests { use super::*; #[test] fn test_with_tilde_no_hd() { fn hd() -> Option { None } assert_eq!(tilde_with_context("whatever", hd), s("whatever")); assert_eq!(tilde_with_context("whatever/~", hd), s("whatever/~")); assert_eq!(tilde_with_context("~/whatever", hd), s("~/whatever")); assert_eq!(tilde_with_context("~", hd), s("~")); assert_eq!(tilde_with_context("~something", hd), s("~something")); } #[test] fn test_with_tilde() { fn hd() -> Option { Some("/home/dir".into()) } assert_eq!(tilde_with_context("whatever/path", hd), s("whatever/path")); assert_eq!(tilde_with_context("whatever/~/path", hd), s("whatever/~/path")); assert_eq!(tilde_with_context("~", hd), s("/home/dir")); assert_eq!(tilde_with_context("~/path", hd), s("/home/dir/path")); assert_eq!(tilde_with_context("~whatever/path", hd), s("~whatever/path")); } #[test] fn test_global_tilde() { match dirs::home_dir() { Some(hd) => assert_eq!(tilde("~/something"), s(&format!("{}/something", hd.display()))), None => assert_eq!(tilde("~/something"), s("~/something")), } } } mod env_test { use super::*; macro_rules! table { ($env:expr, unwrap, $($source:expr => $target:expr),+) => { $( assert_eq!(env_with_context($source, $env).unwrap(), s($target)); )+ }; ($env:expr, error, $($source:expr => $name:expr),+) => { $( assert_eq!(env_with_context($source, $env), Err(LookupError { var_name: mk_var_name($name), cause: () })); )+ } } #[test] fn test_empty_env() { fn e(_: &str) -> Result, ()> { Ok(None) } table! { e, unwrap, "whatever/path" => "whatever/path", "$VAR/whatever/path" => "$VAR/whatever/path", "whatever/$VAR/path" => "whatever/$VAR/path", "whatever/path/$VAR" => "whatever/path/$VAR", "${VAR}/whatever/path" => "${VAR}/whatever/path", "whatever/${VAR}path" => "whatever/${VAR}path", "whatever/path/${VAR}" => "whatever/path/${VAR}", "${}/whatever/path" => "${}/whatever/path", "whatever/${}path" => "whatever/${}path", "whatever/path/${}" => "whatever/path/${}", "$/whatever/path" => "$/whatever/path", "whatever/$path" => "whatever/$path", "whatever/path/$" => "whatever/path/$", "$$/whatever/path" => "$/whatever/path", "whatever/$$path" => "whatever/$path", "whatever/path/$$" => "whatever/path/$", "$A$B$C" => "$A$B$C", "$A_B_C" => "$A_B_C" }; } #[test] fn test_error_env() { fn e(_: &str) -> Result, ()> { Err(()) } table! { e, unwrap, "whatever/path" => "whatever/path", // check that escaped $ does nothing "whatever/$/path" => "whatever/$/path", "whatever/path$" => "whatever/path$", "whatever/$$path" => "whatever/$path" }; table! { e, error, "$VAR/something" => "VAR", "${VAR}/something" => "VAR", "whatever/${VAR}/something" => "VAR", "whatever/${VAR}" => "VAR", "whatever/$VAR/something" => "VAR", "whatever/$VARsomething" => "VARsomething", "whatever/$VAR" => "VAR", "whatever/$VAR_VAR_VAR" => "VAR_VAR_VAR" }; } #[test] fn test_regular_env() { fn e(s: &str) -> Result, ()> { match s { "VAR" => Ok(Some("value")), "a_b" => Ok(Some("X_Y")), "EMPTY" => Ok(Some("")), "ERR" => Err(()), _ => Ok(None), } } table! { e, unwrap, // no variables "whatever/path" => "whatever/path", // empty string "" => "", // existing variable without braces in various positions "$VAR/whatever/path" => "value/whatever/path", "whatever/$VAR/path" => "whatever/value/path", "whatever/path/$VAR" => "whatever/path/value", "whatever/$VARpath" => "whatever/$VARpath", "$VAR$VAR/whatever" => "valuevalue/whatever", "/whatever$VAR$VAR" => "/whatevervaluevalue", "$VAR $VAR" => "value value", "$a_b" => "X_Y", "$a_b$VAR" => "X_Yvalue", // existing variable with braces in various positions "${VAR}/whatever/path" => "value/whatever/path", "whatever/${VAR}/path" => "whatever/value/path", "whatever/path/${VAR}" => "whatever/path/value", "whatever/${VAR}path" => "whatever/valuepath", "${VAR}${VAR}/whatever" => "valuevalue/whatever", "/whatever${VAR}${VAR}" => "/whatevervaluevalue", "${VAR} ${VAR}" => "value value", "${VAR}$VAR" => "valuevalue", // default values "/answer/${UNKNOWN:-42}" => "/answer/42", "/answer/${:-42}" => "/answer/${:-42}", "/whatever/${UNKNOWN:-other}$VAR" => "/whatever/othervalue", "/whatever/${UNKNOWN:-other}/$VAR" => "/whatever/other/value", ":-/whatever/${UNKNOWN:-other}/$VAR :-" => ":-/whatever/other/value :-", "/whatever/${VAR:-other}" => "/whatever/value", "/whatever/${VAR:-other}$VAR" => "/whatever/valuevalue", "/whatever/${VAR} :-" => "/whatever/value :-", "/whatever/${:-}" => "/whatever/${:-}", "/whatever/${UNKNOWN:-}" => "/whatever/", // empty variable in various positions "${EMPTY}/whatever/path" => "/whatever/path", "whatever/${EMPTY}/path" => "whatever//path", "whatever/path/${EMPTY}" => "whatever/path/" }; table! { e, error, "$ERR" => "ERR", "${ERR}" => "ERR" }; } #[test] fn test_unicode() { fn e(s: &str) -> Result, ()> { match s { "élan" => Ok(Some("ἐκθυμία")), _ => Ok(None), } } table! { e, unwrap, "plain" => "plain", "with $élan lacking" => "with ἐκθυμία lacking", "with ${élan} enclosed" => "with ἐκθυμία enclosed" }; } #[test] fn test_global_env() { match std::env::var("PATH") { Ok(value) => assert_eq!(env("x/$PATH/x").unwrap(), s(&format!("x/{}/x", value))), Err(e) => assert_eq!( env("x/$PATH/x"), Err(LookupError { var_name: mk_var_name("PATH"), cause: e }) ), } match std::env::var("SOMETHING_DEFINITELY_NONEXISTING") { Ok(value) => assert_eq!( env("x/$SOMETHING_DEFINITELY_NONEXISTING/x").unwrap(), s(&format!("x/{}/x", value)) ), Err(e) => assert_eq!( env("x/$SOMETHING_DEFINITELY_NONEXISTING/x"), Err(LookupError { var_name: mk_var_name("SOMETHING_DEFINITELY_NONEXISTING"), cause: e }) ), } } } mod full_tests { use super::*; #[test] fn test_quirks() { fn hd() -> Option { Some("$VAR".into()) } fn env(s: &str) -> Result, ()> { match s { "VAR" => Ok(Some("value")), "SVAR" => Ok(Some("/value")), "TILDE" => Ok(Some("~")), _ => Ok(None), } } // any variable-like sequence in ~ expansion should not trigger variable expansion assert_eq!( full_with_context("~/something/$VAR", hd, env).unwrap(), s("$VAR/something/value") ); // variable just after tilde should be substituted first and trigger regular tilde // expansion assert_eq!(full_with_context("~$VAR", hd, env).unwrap(), s("~value")); assert_eq!(full_with_context("~$SVAR", hd, env).unwrap(), s("$VAR/value")); // variable expanded into a tilde in the beginning should not trigger tilde expansion assert_eq!( full_with_context("$TILDE/whatever", hd, env).unwrap(), s("~/whatever") ); assert_eq!( full_with_context("${TILDE}whatever", hd, env).unwrap(), s("~whatever") ); assert_eq!(full_with_context("$TILDE", hd, env).unwrap(), s("~")); } #[test] fn test_tilde_expansion() { fn hd() -> Option { Some("/home/user".into()) } assert_eq!( tilde_with_context("~/some/dir", hd), s("/home/user/some/dir") ); } #[cfg(target_family = "windows")] #[test] fn test_tilde_expansion_windows() { fn home_dir() -> Option { Some(Path::new("C:\\users\\public").into()) } assert_eq!( tilde_with_context("~\\some\\dir", home_dir), "C:\\users\\public\\some\\dir" ); } } shellexpand-3.1.0/src/wtraits.rs000064400000000000000000000170041046102023000147700ustar 00000000000000//! Type aliases and trait definitions for internal traits - **instantiated twice** //! //! Like `funcs`, this is instantiated twice, as //! [`crate::strings::wtraits`] and [`crate::path::wtraits`]. //! //! He type aliases, and even traits, here, //! are all different, in the two contexts. #![cfg_attr(not(feature = "full"), allow(dead_code))] pub(crate) use std::borrow::Cow; pub(crate) use std::fmt; pub(crate) use std::path::PathBuf; /// External borrowed form; inputs are generally of type `AsRef` /// /// `str` for strings; [`Path`](std::path::Path) for paths. pub(crate) type Xstr = super::Xstr; /// Working, possibly-owned, form of the input /// /// Usually converted from `AsRef<`[`Xstr`]`>`, /// using [`.into_winput()`](AsRefXstrExt::into_winput). /// /// For strings, this is simply `&'s str`; /// /// For paths, this is `Cow<'s, RawOsStr>`. /// This is because it can be necessary to copy-convert the input /// to a different representation. /// (This happens only on Windows, whose native character encoding UTF-16, which is deranged; /// on Unix, I believe this is always `Cow::Borrowed`.) pub(crate) type WInput<'s> = super::WInput<'s>; /// Working, definitely-borrowed, form of the input /// /// Most input-processing, such as slicing up the input, is done with this type. /// /// `str` for strings, `RawOsStr` for paths. pub(crate) type Wstr = super::Wstr; /// Output accumulator (owned) /// /// As we process input chunks, we add them to this. /// /// This type doesn't provide an easy way to rescan it again - /// but we generally only want to scan things once. /// /// Also used for error reporting, in `LookupError`. /// /// `String`, or [`OsString`](std::ffi::OsString). pub(crate) type OString = super::OString; /// Owned version of Xstr /// /// `String`, or `PathBuf`. /// /// Currently used only as the return value from `home_dir()`. pub(crate) type XString = ::Owned; /// Extra bounds on [`Xstr`] (and hence, restrictions on [`XString`]) /// /// This is implemented only for the single type `Xstr` (ie, for `str`, or `Path`). /// /// The bound `str: AsRef` /// declares that we can cheaply borrow a normal string as an `Xstr`. /// /// The bound `PathBuf: `[`PathBufExt`] /// declares that /// `Pathbuf.`[`try_into_xstring()`](PathBufExt::try_into_xstring) is available - /// ie, that you can try to obtain an `XString` from a `PathBuf`, /// which is relevant for home directories and tilde substitution. /// /// These bounds (and therefore this trait), including the supertrait, /// are not needed for the code to compile, /// since we don't have significant amounts of generic code. /// But they serve as compiler-checked documentation. pub(crate) trait XstrRequirements: AsRefXstrExt where str: AsRef, PathBuf: PathBufExt { } /// Methods on `AsRef<`[`Xstr`]`>` /// /// These methods are used by the implementation in `funcs` /// to convert the input into the working form, /// or, to be able to return it unmodified. /// /// This is implemented (only) for `S: AsRef`. pub(crate) trait AsRefXstrExt { /// Convert an input string into our working form fn into_winput(&self) -> WInput; /// Convert an unmodified input string back into the public output type /// /// Called when inspection of the string tells us we didn't need to make any substitutions. fn into_ocow(&self) -> Cow<'_, Xstr>; } /// Extension trait on `PathBuf` /// /// Implemented separately by the code for /// strings (returning `Option`) /// and paths (returning `Option`) pub(crate) trait PathBufExt { /// Converts a `PathBuf` into an `XString` (input string type), if possible /// /// We might not be able to represent a non-Unicode path. /// In that case, this function returns `None`. fn try_into_xstring(self) -> Option; } /// Methods (and bounds) on `Wstr` /// /// `funcs` may also use inherent methods, including slicing, /// provided they are implemented for both `str` and `RawOsString`, /// with suitable semantics. /// /// The bound declares that `&str` or `&RawOsStr` implements `WstrRefExt` /// so that [`.chars_approx()`](WstrRefExt::chars_approx) is available. pub(crate) trait WstrExt: where for <'s> &'s Self: WstrRefExt { /// Turn something which maybe derefs to a `Wstr` into a `Wstr` /// /// This allows us to use method call autoderef. /// /// We want this at call sites that have [`WInput`], /// which don't otherwise know whether to deref: /// for strings, `WInput` is `&str`, /// and is therefore already a `&Wstr` (which is also `&str`). /// whereas for paths, `WInput` is `Cow` which derefs to `&Wstr` (ie `&Path`). fn as_wstr(&self) -> &Self { self } /// Get this bit of the input as a plain string, if it is Unicode /// /// Used for variable name lookups. Our variable lookup API passes Unicode variable names /// to the caller. /// (Any non-Unicode we find is therefore, by definition, an undefined variable). fn as_str(&self) -> Option<&str>; /// Length, in a form suitable for slicing /// /// Calling this `len` is only reasonable because the `Wstr` type either /// doesn't have a `len` method of its own (true of `RawOsStr`, for paths), /// or has one which works the same (true of `str`, for strings). fn len(&self) -> usize; /// Convert to an `OString`, as used for error reporting etc. fn to_ostring(&self) -> OString; /// Like `str::strip_prefix`, but available on our str MSRV 1.31 fn strip_prefix(&self, c: char) -> Option<&Self>; } /// Method on the reference [`&Wstr`](Wstr) /// /// This can't be in the main [`WstrExt`] trait because /// the type `Chars` would have a lifetime - /// ie it would be a GAT, which is very new in Rust and we don't want to rely on. pub(crate) trait WstrRefExt { /// Iterator over characters type Chars: Iterator + CharsExt; /// Iterate over, roughly, characters /// /// This is not guaranteed to work very well in the presence of non-Unicode input, /// or strange encodings. /// /// The properties we rely on are: /// /// * Where the input contains ASCII characters `$` `{` `}` `~` `/` `\`, /// they come out of the iterator in the right place. /// /// * Where the input contains characters that are found in variable names the user /// wants to substitute, they come out properly in the right place, /// and they are recognised as Unicode letters. /// /// * The return value from [`.wstr_len()`](CharsExt::wstr_len) /// can be used in slicing calculations. fn chars_approx(self) -> Self::Chars; } /// Methods on the characters iterator from [`Wstr.chars_approx()`](WstrRefExt::chars_approx) pub(crate) trait CharsExt { fn wstr_len(&self) -> usize; } /// Methods on [`OString`] /// /// `funcs` may also use inherent methods, /// provided they are implemented for both `String` and `RawOsString`, /// with suitable semantics. pub(crate) trait OStringExt { /// Append a plain `str` (used for literal text) fn push_str(&mut self, x: &str); /// Append a `Wstr` (used for copying through pieces of the input) fn push_wstr(&mut self, x: &Wstr); /// Append an `Xstr` (used for copying through variable lookup results) fn push_xstr(&mut self, x: &Xstr); /// Convert an output string we have been accumulating into the public output type fn into_ocow(self) -> Cow<'static, Xstr>; /// Write this input string in a possibly-lossy way, for use in error messages fn display_possibly_lossy(&self, f: &mut fmt::Formatter) -> fmt::Result; }