diff -Nru rust-path-slash-0.1.4/Cargo.toml rust-path-slash-0.2.1/Cargo.toml --- rust-path-slash-0.1.4/Cargo.toml 2021-01-15 14:33:28.000000000 +0000 +++ rust-path-slash-0.2.1/Cargo.toml 1970-01-01 00:00:01.000000000 +0000 @@ -3,28 +3,32 @@ # 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 +# to registry (e.g., crates.io) dependencies. # -# If you believe there's an error in this file please file an -# issue against the rust-lang/cargo repository. If you're -# editing this file be aware that the upstream Cargo.toml -# will likely look very different (and much more reasonable) +# 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" +rust-version = "1.38" name = "path-slash" -version = "0.1.4" +version = "0.2.1" authors = ["rhysd "] description = "Conversion to/from a file path from/to slash path" readme = "README.md" +keywords = ["path"] categories = ["filesystem"] license = "MIT" repository = "https://github.com/rhysd/path-slash" + [package.metadata.release] -no-dev-version = true +dev-version = false [dependencies] + [dev-dependencies.lazy_static] version = "1" + [badges.maintenance] status = "passively-maintained" diff -Nru rust-path-slash-0.1.4/Cargo.toml.orig rust-path-slash-0.2.1/Cargo.toml.orig --- rust-path-slash-0.1.4/Cargo.toml.orig 2021-01-15 14:33:27.000000000 +0000 +++ rust-path-slash-0.2.1/Cargo.toml.orig 1973-11-29 21:33:09.000000000 +0000 @@ -1,6 +1,6 @@ [package] name = "path-slash" -version = "0.1.4" +version = "0.2.1" authors = ["rhysd "] edition = "2018" description = "Conversion to/from a file path from/to slash path" @@ -8,12 +8,14 @@ readme = "README.md" license = "MIT" categories = ["filesystem"] +keywords = ["path"] +rust-version = "1.38" [badges] maintenance = { status = "passively-maintained" } [package.metadata.release] -no-dev-version = true +dev-version = false [dependencies] diff -Nru rust-path-slash-0.1.4/.cargo_vcs_info.json rust-path-slash-0.2.1/.cargo_vcs_info.json --- rust-path-slash-0.1.4/.cargo_vcs_info.json 2021-01-15 14:33:28.000000000 +0000 +++ rust-path-slash-0.2.1/.cargo_vcs_info.json 1970-01-01 00:00:01.000000000 +0000 @@ -1,5 +1,6 @@ { "git": { - "sha1": "0f0dd48af741f32d4750706eededd5871965a9b3" - } -} + "sha1": "222a3add08b43a424d2d4dbaacf014fc68174063" + }, + "path_in_vcs": "" +} \ No newline at end of file diff -Nru rust-path-slash-0.1.4/CHANGELOG.md rust-path-slash-0.2.1/CHANGELOG.md --- rust-path-slash-0.1.4/CHANGELOG.md 2020-09-24 09:03:48.000000000 +0000 +++ rust-path-slash-0.2.1/CHANGELOG.md 1973-11-29 21:33:09.000000000 +0000 @@ -1,3 +1,78 @@ + +# [v0.2.0](https://github.com/rhysd/path-slash/releases/tag/v0.2.0) - 05 Jul 2022 + +- **BREAKING:** `to_slash` and `to_slash_lossy` return `Cow<'_, str>` instead of `String`. Now heap allocation hapnens only when path separator is replaced. On Unix-like OS, almost all heap allocations can be removed by this change. Migrating from 0.1 to 0.2 is easy by adding `Cow::into_owned` call. (#9) + ```rust + use path_slash::PathExt as _; + + // 0.1 + let s: Option = Path::new("/a/b").to_slash(); + let s: String = Path::new("/a/b").to_slash_lossy(); + // 0.2 + let s: Option = Path::new("/a/b").to_slash().map(Cow::into_owned); + let s: String = Path::new("/a/b").to_slash_lossy().into_owned(); + ``` + API changes are as follows: + - 0.1.5 + - `Path::to_slash(&self) -> Option` + - `Path::to_slash_lossy(&self) -> String` + - 0.2.0 + - `Path::to_slash(&self) -> Option>` + - `Path::to_slash_lossy(&self) -> Cow<'_, Path>` +- **BREAKING:** Fix inconsistency on Windows and on Unix-like OS in terms of trailing slash in path. Now a trailing slash in path is always preserved. (#10) + ```rust + // 0.1 + #[cfg(target_os = "windows")] + assert_eq!(Path::new(r"\a\b\").to_slash_lossy(), "/a/b"); // Trailing slash is removed + #[cfg(not(target_os = "windows"))] + assert_eq!(Path::new(r"\a\b\").to_slash_lossy(), "/a/b/"); // Trailing slash is preserved + + // 0.2 + #[cfg(target_os = "windows")] + assert_eq!(Path::new(r"\a\b\").to_slash_lossy(), "/a/b/"); // Trailing slash is preserved + #[cfg(not(target_os = "windows"))] + assert_eq!(Path::new(r"\a\b\").to_slash_lossy(), "/a/b/"); // Trailing slash is preserved + ``` +- New API `path_slash::CowExt` is added to extend `Cow<'_, Path>`. Methods to convert slash paths to `Cow<'_, Path>` are available. It is useful to avoid heap allocation as much as possible comparing with `PathBufExt`. See [the API document](https://docs.rs/path-slash/latest/path_slash/trait.CowExt.html) for more details. (#9) + ```rust + use path_slash::CowExt as _; + let p = Cow::from_slash("foo/bar/piyo.txt"); // Heap allocation only happens on Windows + + #[cfg(target_os = "windows")] + assert_eq!(p, Cow::Owned(PathBuf::from(r"foo\bar\piyo.txt"))); + #[cfg(not(target_os = "windows"))] + assert_eq!(p, Cow::Borrowed(Path::new("foo/bar/piyo.txt"))); + ``` + All methods added by importing `CowExt` are as follows: + - `Cow::::from_slash(s: &str) -> Self` + - `Cow::::from_slash_lossy(s: &OsStr) -> Self` + - `Cow::::from_backslash(s: &str) -> Self` + - `Cow::::from_backslash_lossy(s: &OsStr) -> Self` +- More tests are added. Now [the line coverage](https://app.codecov.io/gh/rhysd/path-slash) is 100%. + - UTF-16 test cases for native encoding on Windows + - All error cases including broken UTF-8 and UTF-16 sequences + +[Changes][v0.2.0] + + + +# [v0.1.5](https://github.com/rhysd/path-slash/releases/tag/v0.1.5) - 29 Jun 2022 + +- Add new APIs to convert backslash paths to `PathBuf`. (#8, thanks @picobyte) + - `PathBuf::from_backslash` converts `&str` into `PathBuf` with replacing `\` on non-Windows OS + - `PathBuf::from_backslash` converts `&OsStr` into `PathBuf` with replacing `\` on non-Windows OS + +[Changes][v0.1.5] + + + +# [v0.1.4](https://github.com/rhysd/path-slash/releases/tag/v0.1.4) - 15 Jan 2021 + +- Fix a final letter of paths with some verbatim prefixes was removed (#5) + +[Changes][v0.1.4] + + # [v0.1.3](https://github.com/rhysd/path-slash/releases/tag/v0.1.3) - 01 Jul 2020 @@ -15,7 +90,10 @@ [Changes][v0.1.2] +[v0.2.0]: https://github.com/rhysd/path-slash/compare/v0.1.5...v0.2.0 +[v0.1.5]: https://github.com/rhysd/path-slash/compare/v0.1.4...v0.1.5 +[v0.1.4]: https://github.com/rhysd/path-slash/compare/v0.1.3...v0.1.4 [v0.1.3]: https://github.com/rhysd/path-slash/compare/v0.1.2...v0.1.3 [v0.1.2]: https://github.com/rhysd/path-slash/tree/v0.1.2 - + diff -Nru rust-path-slash-0.1.4/.codecov.yaml rust-path-slash-0.2.1/.codecov.yaml --- rust-path-slash-0.1.4/.codecov.yaml 1970-01-01 00:00:00.000000000 +0000 +++ rust-path-slash-0.2.1/.codecov.yaml 1973-11-29 21:33:09.000000000 +0000 @@ -0,0 +1,7 @@ +# https://docs.codecov.com/docs/commit-status#disabling-a-status +coverage: + status: + project: off + patch: off + +comment: false diff -Nru rust-path-slash-0.1.4/debian/cargo-checksum.json rust-path-slash-0.2.1/debian/cargo-checksum.json --- rust-path-slash-0.1.4/debian/cargo-checksum.json 2022-06-24 00:16:48.000000000 +0000 +++ rust-path-slash-0.2.1/debian/cargo-checksum.json 2022-12-27 09:13:47.000000000 +0000 @@ -1 +1 @@ -{"package":"3cacbb3c4ff353b534a67fb8d7524d00229da4cb1dc8c79f4db96e375ab5b619","files":{}} +{"package":"1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42","files":{}} diff -Nru rust-path-slash-0.1.4/debian/changelog rust-path-slash-0.2.1/debian/changelog --- rust-path-slash-0.1.4/debian/changelog 2022-06-24 00:16:48.000000000 +0000 +++ rust-path-slash-0.2.1/debian/changelog 2022-12-27 09:13:47.000000000 +0000 @@ -1,3 +1,10 @@ +rust-path-slash (0.2.1-1) unstable; urgency=medium + + * Team upload. + * Package path-slash 0.2.1 from crates.io using debcargo 2.6.0 (Closes: #1025911) + + -- Peter Michael Green Tue, 27 Dec 2022 09:13:47 +0000 + rust-path-slash (0.1.4-1) unstable; urgency=medium * Team upload. diff -Nru rust-path-slash-0.1.4/debian/control rust-path-slash-0.2.1/debian/control --- rust-path-slash-0.1.4/debian/control 2022-06-24 00:16:48.000000000 +0000 +++ rust-path-slash-0.2.1/debian/control 2022-12-27 09:13:47.000000000 +0000 @@ -9,9 +9,10 @@ Maintainer: Debian Rust Maintainers Uploaders: Sylvestre Ledru -Standards-Version: 4.5.1 +Standards-Version: 4.6.1 Vcs-Git: https://salsa.debian.org/rust-team/debcargo-conf.git [src/path-slash] Vcs-Browser: https://salsa.debian.org/rust-team/debcargo-conf/tree/master/src/path-slash +X-Cargo-Crate: path-slash Rules-Requires-Root: no Package: librust-path-slash-dev @@ -23,10 +24,10 @@ librust-path-slash+default-dev (= ${binary:Version}), librust-path-slash-0-dev (= ${binary:Version}), librust-path-slash-0+default-dev (= ${binary:Version}), - librust-path-slash-0.1-dev (= ${binary:Version}), - librust-path-slash-0.1+default-dev (= ${binary:Version}), - librust-path-slash-0.1.4-dev (= ${binary:Version}), - librust-path-slash-0.1.4+default-dev (= ${binary:Version}) + librust-path-slash-0.2-dev (= ${binary:Version}), + librust-path-slash-0.2+default-dev (= ${binary:Version}), + librust-path-slash-0.2.1-dev (= ${binary:Version}), + librust-path-slash-0.2.1+default-dev (= ${binary:Version}) Description: Conversion to/from a file path from/to slash path - Rust source code This package contains the source for the Rust path-slash crate, packaged by debcargo for use with cargo and dh-cargo. diff -Nru rust-path-slash-0.1.4/debian/copyright.debcargo.hint rust-path-slash-0.2.1/debian/copyright.debcargo.hint --- rust-path-slash-0.1.4/debian/copyright.debcargo.hint 2022-06-24 00:16:48.000000000 +0000 +++ rust-path-slash-0.2.1/debian/copyright.debcargo.hint 2022-12-27 09:13:47.000000000 +0000 @@ -12,7 +12,7 @@ be correct information so you should review and fix this before uploading to the archive. -Files: ./LICENSE.txt +Files: LICENSE.txt Copyright: 2018 rhysd License: UNKNOWN-LICENSE; FIXME (overlay) Comment: diff -Nru rust-path-slash-0.1.4/debian/tests/control rust-path-slash-0.2.1/debian/tests/control --- rust-path-slash-0.1.4/debian/tests/control 2022-06-24 00:16:48.000000000 +0000 +++ rust-path-slash-0.2.1/debian/tests/control 2022-12-27 09:13:47.000000000 +0000 @@ -1,14 +1,14 @@ -Test-Command: /usr/share/cargo/bin/cargo-auto-test path-slash 0.1.4 --all-targets --all-features +Test-Command: /usr/share/cargo/bin/cargo-auto-test path-slash 0.2.1 --all-targets --all-features Features: test-name=rust-path-slash:@ Depends: dh-cargo (>= 18), librust-lazy-static-1+default-dev, @ Restrictions: allow-stderr, skip-not-installable -Test-Command: /usr/share/cargo/bin/cargo-auto-test path-slash 0.1.4 --all-targets +Test-Command: /usr/share/cargo/bin/cargo-auto-test path-slash 0.2.1 --all-targets Features: test-name=librust-path-slash-dev:default Depends: dh-cargo (>= 18), librust-lazy-static-1+default-dev, @ Restrictions: allow-stderr, skip-not-installable -Test-Command: /usr/share/cargo/bin/cargo-auto-test path-slash 0.1.4 --all-targets --no-default-features +Test-Command: /usr/share/cargo/bin/cargo-auto-test path-slash 0.2.1 --all-targets --no-default-features Features: test-name=librust-path-slash-dev: Depends: dh-cargo (>= 18), librust-lazy-static-1+default-dev, @ Restrictions: allow-stderr, skip-not-installable diff -Nru rust-path-slash-0.1.4/.github/workflows/ci.yaml rust-path-slash-0.2.1/.github/workflows/ci.yaml --- rust-path-slash-0.1.4/.github/workflows/ci.yaml 2020-09-24 09:03:48.000000000 +0000 +++ rust-path-slash-0.2.1/.github/workflows/ci.yaml 1973-11-29 21:33:09.000000000 +0000 @@ -1,6 +1,10 @@ name: CI on: [push, pull_request] +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + jobs: unit-test: name: unit tests @@ -10,23 +14,52 @@ fail-fast: false runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Rust toolchain run: | rustup set profile minimal - rustup update stable + rustup update stable --no-self-update rustup default stable + rustup component add llvm-tools-preview rustup show cargo --version + - uses: Swatinem/rust-cache@v1 + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + - name: Run tests with measuring coverage + run: | + cargo llvm-cov --color always --lcov --output-path lcov.info + cargo llvm-cov --color always --no-run + shell: bash + - uses: codecov/codecov-action@v3 + with: + files: lcov.info + + msrv: + name: MSRV (Rust 1.38) + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Install Rust toolchain + run: | + rustup set profile minimal + rustup toolchain install 1.38.0 + rustup default 1.38.0 + rustup show + cargo --version + - uses: Swatinem/rust-cache@v1 - name: Run tests - env: - RUST_BACKTRACE: 1 - run: cargo test --color always --all + run: cargo test --color always + linter: name: clippy and rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install tools run: | rustup set profile minimal @@ -37,5 +70,6 @@ cargo --version cargo fmt --version cargo clippy --version + - uses: Swatinem/rust-cache@v1 - run: cargo fmt -- --color always --check - run: cargo clippy --color always --all -- -D warnings diff -Nru rust-path-slash-0.1.4/README.md rust-path-slash-0.2.1/README.md --- rust-path-slash-0.1.4/README.md 2020-12-03 01:51:47.000000000 +0000 +++ rust-path-slash-0.2.1/README.md 1973-11-29 21:33:09.000000000 +0000 @@ -3,55 +3,82 @@ [![crates.io][crates-io-badge]][crates-io] [![documentation][doc-badge]][doc] [![CI][ci-badge]][ci] +[![codecov-badge][]][codecov] [`path-slash`][crates-io] is a tiny library to convert a file path (e.g. `foo/bar`, `foo\bar` or `C:\foo\bar`) from/to slash path (e.g. `foo/bar`, `C:/foo/bar`). -On Unix-like OS, path separator is slash `/` by default. So any conversion is not necessary. But on +On Unix-like OS, path separator is slash `/` by default. One may want to convert a Windows path. But on Windows, file path separator `\` needs to be replaced with slash `/` (and of course `\`s for escaping characters should not be replaced). +Supported Rust version is 1.38.0 or later. + This package was inspired by Go's [`path/filepath.FromSlash`](https://golang.org/pkg/path/filepath/#FromSlash) and [`path/filepath.ToSlash`](https://golang.org/pkg/path/filepath/#ToSlash). ## Usage -`path_slash::PathExt` and `path_slash::PathBufExt` traits are defined. By using them, `std::path::Path` -and `std::path::PathBuf` gains some methods and associated functions +`path_slash::PathExt`, `path_slash::PathBufExt` and `path_slash::CowExt` traits are defined. By using them, +`std::path::Path`, `std::path::PathBuf` and `std::borrow::Cow<'_, Path>` gain some methods and associated functions. - `PathExt` - - `Path::to_slash(&self) -> Option` - - `Path::to_slash_lossy(&self) -> String` + - `Path::to_slash(&self) -> Option>` + - `Path::to_slash_lossy(&self) -> Cow<'_, Path>` - `PathBufExt` - `PathBuf::from_slash>(s: S) -> PathBuf` - `PathBuf::from_slash_lossy>(s: S) -> PathBuf` - - `PathBuf::to_slash(&self) -> Option` - - `PathBuf::to_slash_lossy(&self) -> String` + - `PathBuf::from_backslash>(s: S) -> PathBuf` + - `PathBuf::from_backslash_lossy>(s: S) -> PathBuf` + - `PathBuf::to_slash(&self) -> Option>` + - `PathBuf::to_slash_lossy(&self) -> Cow<'_, Path>` +- `CowExt` + - `Cow::::from_slash(s: &str) -> Self` + - `Cow::::from_slash_lossy(s: &OsStr) -> Self` + - `Cow::::from_backslash(s: &str) -> Self` + - `Cow::::from_backslash_lossy(s: &OsStr) -> Self` ```rust fn example_path_ext() { // Trait for extending std::path::Path - use path_slash::PathExt; + use path_slash::PathExt as _; + + let p = Path::from_slash("foo/bar/piyo.txt"); // On Windows - assert_eq!( - Path::new(r"foo\bar\piyo.txt").to_slash(), - Some("foo/bar/piyo.txt".to_string()), - ); - assert_eq!( - Path::new(r"C:\foo\bar\piyo.txt").to_slash(), - Some("C:/foo/bar/piyo.txt".to_string()), - ); + assert_eq!(p, Path::new(r"foo\bar\piyo.txt")); + + // Convert to slash path + assert_eq!(p.to_slash().unwrap(), "foo/bar/piyo.txt"); + assert_eq!(p.to_slash_lossy(), "foo/bar/piyo.txt"); } fn example_pathbuf_ext() { // Trait for extending std::path::PathBuf - use path_slash::PathBufExt; + use path_slash::PathBufExt as _; // On Windows let p = PathBuf::from_slash("foo/bar/piyo.txt"); assert_eq!(p, PathBuf::from(r"foo\bar\piyo.txt")); - assert_eq!(p.to_slash(), Some("foo/bar/piyo.txt".to_string())); + + // Convert to slash path + assert_eq!(p.to_slash().unwrap(), "foo/bar/piyo.txt"); + assert_eq!(p.to_slash_lossy(), "foo/bar/piyo.txt"); +} + +fn example_cow_ext() { + // Trait for extending std::borrow::Cow<'_, Path> + use path_slash::CowExt as _; + + let p = Cow::from_slash("foo/bar/piyo.txt"); + // On Windows + assert_eq!(p, Cow::Owned(PathBuf::from(r"foo\bar\piyo.txt"))); + // On non-Windows + assert_eq!(p, Cow::Borrowed(Path::new("foo/bar/piyo.txt"))); + + // Convert to slash path + assert_eq!(p.to_slash().unwrap(), "foo/bar/piyo.txt"); + assert_eq!(p.to_slash_lossy(), "foo/bar/piyo.txt"); } ``` @@ -76,3 +103,5 @@ [crates-io]: https://crates.io/crates/path-slash [ci]: https://github.com/rhysd/path-slash/actions?query=workflow%3ACI [ci-badge]: https://github.com/rhysd/path-slash/workflows/CI/badge.svg?branch=master&event=push +[codecov-badge]: https://codecov.io/gh/rhysd/path-slash/branch/master/graph/badge.svg?token=6f7QWopfz4 +[codecov]: https://codecov.io/gh/rhysd/path-slash diff -Nru rust-path-slash-0.1.4/src/lib.rs rust-path-slash-0.2.1/src/lib.rs --- rust-path-slash-0.1.4/src/lib.rs 2021-01-15 14:27:28.000000000 +0000 +++ rust-path-slash-0.2.1/src/lib.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,88 +1,143 @@ -//! A library for converting file paths to and from "slash paths." +//! A library for converting file paths to and from "slash paths". //! //! A "slash path" is a path whose components are always separated by `/` and never `\`. //! -//! On Unix-like OSes, the path separator is `/`. So any conversion is not necessary. -//! But on Windows, the file path separator is `\`, and needs to be replaced with `/`. Of course, `\`s used -//! for escaping characters should not be replaced. +//! On Unix-like OS, the path separator is `/`. So any conversion is not necessary. +//! But on Windows, the file path separator is `\`, and needs to be replaced with `/` for converting +//! the paths to "slash paths". Of course, `\`s used for escaping characters should not be replaced. //! //! For example, a file path `foo\bar\piyo.txt` can be converted to/from a slash path `foo/bar/piyo.txt`. //! +//! Supported Rust version is 1.38.0 or later. +//! //! This package was inspired by Go's [`path/filepath.FromSlash`](https://golang.org/pkg/path/filepath/#FromSlash) //! and [`path/filepath.ToSlash`](https://golang.org/pkg/path/filepath/#ToSlash). //! //! ```rust //! use std::path::{Path, PathBuf}; +//! use std::borrow::Cow; //! //! // Trait for extending std::path::Path -//! use path_slash::PathExt; +//! use path_slash::PathExt as _; //! // Trait for extending std::path::PathBuf -//! use path_slash::PathBufExt; +//! use path_slash::PathBufExt as _; +//! // Trait for extending std::borrow::Cow +//! use path_slash::CowExt as _; //! //! #[cfg(target_os = "windows")] //! { +//! // Convert from `Path` //! assert_eq!( -//! Path::new(r"foo\bar\piyo.txt").to_slash(), -//! Some("foo/bar/piyo.txt".to_string()), -//! ); -//! assert_eq!( -//! Path::new(r"C:\foo\bar\piyo.txt").to_slash(), -//! Some("C:/foo/bar/piyo.txt".to_string()), +//! Path::new(r"foo\bar\piyo.txt").to_slash().unwrap(), +//! "foo/bar/piyo.txt", //! ); //! +//! // Convert to/from PathBuf //! let p = PathBuf::from_slash("foo/bar/piyo.txt"); //! assert_eq!(p, PathBuf::from(r"foo\bar\piyo.txt")); -//! assert_eq!(p.to_slash(), Some("foo/bar/piyo.txt".to_string())); +//! assert_eq!(p.to_slash().unwrap(), "foo/bar/piyo.txt"); +//! +//! // Convert to/from Cow<'_, Path> +//! let p = Cow::from_slash("foo/bar/piyo.txt"); +//! assert_eq!(p, Cow::::Owned(PathBuf::from(r"foo\bar\piyo.txt"))); +//! assert_eq!(p.to_slash().unwrap(), "foo/bar/piyo.txt"); //! } //! //! #[cfg(not(target_os = "windows"))] //! { +//! // Convert from `Path` //! assert_eq!( -//! Path::new("foo/bar/piyo.txt").to_slash(), -//! Some("foo/bar/piyo.txt".to_string()), -//! ); -//! assert_eq!( -//! Path::new("/foo/bar/piyo.txt").to_slash(), -//! Some("/foo/bar/piyo.txt".to_string()), +//! Path::new("foo/bar/piyo.txt").to_slash().unwrap(), +//! "foo/bar/piyo.txt", //! ); //! +//! // Convert to/from PathBuf //! let p = PathBuf::from_slash("foo/bar/piyo.txt"); -//! assert_eq!(p, PathBuf::from(r"foo/bar/piyo.txt")); -//! assert_eq!(p.to_slash(), Some("foo/bar/piyo.txt".to_string())); +//! assert_eq!(p, PathBuf::from("foo/bar/piyo.txt")); +//! assert_eq!(p.to_slash().unwrap(), "foo/bar/piyo.txt"); +//! +//! // Convert to/from Cow<'_, Path> +//! let p = Cow::from_slash("foo/bar/piyo.txt"); +//! assert_eq!(p, Cow::Borrowed(Path::new("foo/bar/piyo.txt"))); +//! assert_eq!(p.to_slash().unwrap(), "foo/bar/piyo.txt"); //! } //! ``` #![forbid(unsafe_code)] #![warn(clippy::dbg_macro, clippy::print_stdout)] +use std::borrow::Cow; use std::ffi::OsStr; -use std::path::{Path, PathBuf}; +use std::path::{Path, PathBuf, MAIN_SEPARATOR}; -/// Trait to extend [`std::path::Path`]. +#[cfg(target_os = "windows")] +mod windows { + use super::*; + use std::os::windows::ffi::OsStrExt as _; + + // Workaround for Windows. There is no way to extract raw byte sequence from `OsStr` (in `Path`). + // And `OsStr::to_string_lossy` may cause extra heap allocation. + pub(crate) fn ends_with_main_sep(p: &Path) -> bool { + p.as_os_str().encode_wide().last() == Some(MAIN_SEPARATOR as u16) + } +} + +fn str_to_path(s: &str, sep: char) -> Cow<'_, Path> { + let mut buf = String::new(); + + for (i, c) in s.char_indices() { + if c == sep { + if buf.is_empty() { + buf.reserve(s.len()); + buf.push_str(&s[..i]); + } + buf.push(MAIN_SEPARATOR); + } else if !buf.is_empty() { + buf.push(c); + } + } + + if buf.is_empty() { + Cow::Borrowed(Path::new(s)) + } else { + Cow::Owned(PathBuf::from(buf)) + } +} + +fn str_to_pathbuf>(s: S, sep: char) -> PathBuf { + let s = s + .as_ref() + .chars() + .map(|c| if c == sep { MAIN_SEPARATOR } else { c }) + .collect::(); + PathBuf::from(s) + // Note: When MAIN_SEPARATOR_STR is stabilized, replace this implementation with the following: + // PathBuf::from(s.as_ref().replace(sep, MAIN_SEPARATOR_STR)) +} + +/// Trait to extend [`Path`]. /// /// ``` -/// use path_slash::PathExt; +/// # use std::path::Path; +/// # use std::borrow::Cow; +/// use path_slash::PathExt as _; /// /// assert_eq!( -/// std::path::Path::new("foo").to_slash(), -/// Some("foo".to_string()), +/// Path::new("foo").to_slash(), +/// Some(Cow::Borrowed("foo")), /// ); /// ``` pub trait PathExt { - fn to_slash(&self) -> Option; - fn to_slash_lossy(&self) -> String; -} - -impl PathExt for Path { - /// Convert the file path into slash path as UTF-8 string. - /// - /// Any file path separators in the file path is replaced with '/'. - /// Any non-Unicode sequences are replaced with U+FFFD. + /// Convert the file path into slash path as UTF-8 string. This method is similar to + /// [`Path::to_str`], but the path separator is fixed to '/'. /// - /// On non-Windows OS, it is equivalent to `to_string_lossy().to_string()` + /// Any file path separators in the file path are replaced with '/'. Only when the replacement + /// happens, heap allocation happens and `Cow::Owned` is returned. + /// When the path contains non-Unicode sequence, this method returns None. /// /// ``` - /// use std::path::Path; - /// use path_slash::PathExt; + /// # use std::path::Path; + /// # use std::borrow::Cow; + /// use path_slash::PathExt as _; /// /// #[cfg(target_os = "windows")] /// let s = Path::new(r"foo\bar\piyo.txt"); @@ -90,23 +145,18 @@ /// #[cfg(not(target_os = "windows"))] /// let s = Path::new("foo/bar/piyo.txt"); /// - /// assert_eq!(s.to_slash_lossy(), "foo/bar/piyo.txt".to_string()); + /// assert_eq!(s.to_slash(), Some(Cow::Borrowed("foo/bar/piyo.txt"))); /// ``` - #[cfg(not(target_os = "windows"))] - fn to_slash_lossy(&self) -> String { - self.to_string_lossy().to_string() - } - - /// Convert the file path into slash path as UTF-8 string. + fn to_slash(&self) -> Option>; + /// Convert the file path into slash path as UTF-8 string. This method is similar to + /// [`Path::to_string_lossy`], but the path separator is fixed to '/'. /// - /// Any file path separators in the file path is replaced with '/'. + /// Any file path separators in the file path are replaced with '/'. /// Any non-Unicode sequences are replaced with U+FFFD. /// - /// On non-Windows OS, it is equivalent to `.to_string_lossy().to_string()`. - /// /// ``` - /// use std::path::Path; - /// use path_slash::PathExt; + /// # use std::path::Path; + /// use path_slash::PathExt as _; /// /// #[cfg(target_os = "windows")] /// let s = Path::new(r"foo\bar\piyo.txt"); @@ -114,156 +164,98 @@ /// #[cfg(not(target_os = "windows"))] /// let s = Path::new("foo/bar/piyo.txt"); /// - /// assert_eq!(s.to_slash_lossy(), "foo/bar/piyo.txt".to_string()); + /// assert_eq!(s.to_slash_lossy(), "foo/bar/piyo.txt"); /// ``` + fn to_slash_lossy(&self) -> Cow<'_, str>; +} + +impl PathExt for Path { + #[cfg(not(target_os = "windows"))] + fn to_slash_lossy(&self) -> Cow<'_, str> { + self.to_string_lossy() + } #[cfg(target_os = "windows")] - fn to_slash_lossy(&self) -> String { - use std::path; + fn to_slash_lossy(&self) -> Cow<'_, str> { + use std::path::Component; let mut buf = String::new(); - let mut has_trailing_slash = false; for c in self.components() { match c { - path::Component::RootDir => { /* empty */ } - path::Component::CurDir => buf.push('.'), - path::Component::ParentDir => buf.push_str(".."), - path::Component::Prefix(ref prefix) => { - let s = prefix.as_os_str(); - match s.to_str() { - Some(ref s) => buf.push_str(s), - None => buf.push_str(&s.to_string_lossy()), - } + Component::RootDir => { /* empty */ } + Component::CurDir => buf.push('.'), + Component::ParentDir => buf.push_str(".."), + Component::Prefix(prefix) => { + buf.push_str(&prefix.as_os_str().to_string_lossy()); // C:\foo is [Prefix, RootDir, Normal]. Avoid C:// continue; } - path::Component::Normal(ref s) => match s.to_str() { - Some(ref s) => buf.push_str(s), - None => buf.push_str(&s.to_string_lossy()), - }, + Component::Normal(s) => buf.push_str(&s.to_string_lossy()), } buf.push('/'); - has_trailing_slash = true; } - if buf != "/" && has_trailing_slash { + if !windows::ends_with_main_sep(self) && buf != "/" && buf.ends_with('/') { buf.pop(); // Pop last '/' } - buf + Cow::Owned(buf) } - /// Convert the file path into slash path as UTF-8 string. - /// - /// Any file path separators in the file path is replaced with '/'. - /// When the path contains non-Unicode sequence, this method returns None. - /// - /// On non-Windows OS, it is equivalent to `.to_str().map(str::to_string)` - /// - /// ``` - /// use std::path::Path; - /// use path_slash::PathExt; - /// - /// #[cfg(target_os = "windows")] - /// let s = Path::new(r"foo\bar\piyo.txt"); - /// - /// #[cfg(not(target_os = "windows"))] - /// let s = Path::new("foo/bar/piyo.txt"); - /// - /// assert_eq!(s.to_slash(), Some("foo/bar/piyo.txt".to_string())); - /// ``` #[cfg(not(target_os = "windows"))] - fn to_slash(&self) -> Option { - self.to_str().map(str::to_string) + fn to_slash(&self) -> Option> { + self.to_str().map(Cow::Borrowed) } - - /// Convert the file path into slash path as UTF-8 string. - /// - /// Any file path separators in the file path is replaced with '/'. - /// When the path contains non-Unicode sequence, this method returns None. - /// - /// On non-Windows OS, it is equivalent to `.to_str().map(str::to_string)` - /// - /// ``` - /// use std::path::Path; - /// use path_slash::PathExt; - /// - /// #[cfg(target_os = "windows")] - /// let s = Path::new(r"foo\bar\piyo.txt"); - /// - /// #[cfg(not(target_os = "windows"))] - /// let s = Path::new("foo/bar/piyo.txt"); - /// - /// assert_eq!(s.to_slash(), Some("foo/bar/piyo.txt".to_string())); - /// ``` #[cfg(target_os = "windows")] - fn to_slash(&self) -> Option { - use std::path; + fn to_slash(&self) -> Option> { + use std::path::Component; let mut buf = String::new(); - let mut has_trailing_slash = false; for c in self.components() { match c { - path::Component::RootDir => { /* empty */ } - path::Component::CurDir => buf.push('.'), - path::Component::ParentDir => buf.push_str(".."), - path::Component::Prefix(ref prefix) => { - if let Some(s) = prefix.as_os_str().to_str() { - buf.push_str(s); - // C:\foo is [Prefix, RootDir, Normal]. Avoid C:// - continue; - } else { - return None; - } - } - path::Component::Normal(ref s) => { - if let Some(s) = s.to_str() { - buf.push_str(s); - } else { - return None; - } + Component::RootDir => { /* empty */ } + Component::CurDir => buf.push('.'), + Component::ParentDir => buf.push_str(".."), + Component::Prefix(prefix) => { + buf.push_str(prefix.as_os_str().to_str()?); + // C:\foo is [Prefix, RootDir, Normal]. Avoid C:// + continue; } + Component::Normal(s) => buf.push_str(s.to_str()?), } buf.push('/'); - has_trailing_slash = true; } - if buf != "/" && has_trailing_slash { + if !windows::ends_with_main_sep(self) && buf != "/" && buf.ends_with('/') { buf.pop(); // Pop last '/' } - Some(buf) + Some(Cow::Owned(buf)) } } -/// Trait to extend [`std::path::PathBuf`]. +/// Trait to extend [`PathBuf`]. /// /// ``` -/// use path_slash::PathBufExt; +/// # use std::path::PathBuf; +/// use path_slash::PathBufExt as _; /// /// assert_eq!( -/// std::path::PathBuf::from_slash("foo/bar/piyo.txt").to_slash(), -/// Some("foo/bar/piyo.txt".to_string()), +/// PathBuf::from_slash("foo/bar/piyo.txt").to_slash().unwrap(), +/// "foo/bar/piyo.txt", /// ); /// ``` pub trait PathBufExt { - fn from_slash>(s: S) -> Self; - fn from_slash_lossy>(s: S) -> Self; - fn to_slash(&self) -> Option; - fn to_slash_lossy(&self) -> String; -} - -impl PathBufExt for PathBuf { - /// Convert the slash path (path separated with '/') to [`std::path::PathBuf`]. + /// Convert the slash path (path separated with '/') to [`PathBuf`]. /// /// Any '/' in the slash path is replaced with the file path separator. - /// The replacements only happen on Windows since the file path separators on other OSes are the - /// same as '/'. + /// The replacements only happen on Windows since the file path separators on Unix-like OS are + /// the same as '/'. /// - /// On non-Windows OS, it is simply equivalent to [`std::path::PathBuf::from`]. + /// On non-Windows OS, it is simply equivalent to [`PathBuf::from`]. /// /// ``` - /// use std::path::PathBuf; - /// use path_slash::PathBufExt; + /// # use std::path::PathBuf; + /// use path_slash::PathBufExt as _; /// /// let p = PathBuf::from_slash("foo/bar/piyo.txt"); /// @@ -273,24 +265,24 @@ /// #[cfg(not(target_os = "windows"))] /// assert_eq!(p, PathBuf::from("foo/bar/piyo.txt")); /// ``` - #[cfg(not(target_os = "windows"))] - fn from_slash>(s: S) -> Self { - PathBuf::from(s.as_ref()) - } - - /// Convert the slash path (path separated with '/') to [`std::path::PathBuf`]. + fn from_slash>(s: S) -> Self; + /// Convert the [`OsStr`] slash path (path separated with '/') to [`PathBuf`]. /// /// Any '/' in the slash path is replaced with the file path separator. - /// The replacements only happen on Windows since the file path separators on other OSes are the - /// same as '/'. + /// The replacements only happen on Windows since the file path separators on Unix-like OS are + /// the same as '/'. /// - /// On non-Windows OS, it is simply equivalent to [`std::path::PathBuf::from`]. + /// On Windows, any non-Unicode sequences are replaced with U+FFFD while the conversion. + /// On non-Windows OS, it is simply equivalent to [`PathBuf::from`] and there is no + /// loss while conversion. /// /// ``` - /// use std::path::PathBuf; - /// use path_slash::PathBufExt; + /// # use std::path::PathBuf; + /// # use std::ffi::OsStr; + /// use path_slash::PathBufExt as _; /// - /// let p = PathBuf::from_slash("foo/bar/piyo.txt"); + /// let s: &OsStr = "foo/bar/piyo.txt".as_ref(); + /// let p = PathBuf::from_slash_lossy(s); /// /// #[cfg(target_os = "windows")] /// assert_eq!(p, PathBuf::from(r"foo\bar\piyo.txt")); @@ -298,127 +290,268 @@ /// #[cfg(not(target_os = "windows"))] /// assert_eq!(p, PathBuf::from("foo/bar/piyo.txt")); /// ``` - #[cfg(target_os = "windows")] - fn from_slash>(s: S) -> Self { - use std::path; - - let s = s - .as_ref() - .chars() - .map(|c| match c { - '/' => path::MAIN_SEPARATOR, - c => c, - }) - .collect::(); - PathBuf::from(s) - } - - /// Convert the slash path (path separated with '/') to [`std::path::PathBuf`]. + fn from_slash_lossy>(s: S) -> Self; + /// Convert the backslash path (path separated with '\\') to [`PathBuf`]. /// - /// Any '/' in the slash path is replaced with the file path separator. - /// The replacements only happen on Windows since the file path separators on other OSes are the - /// same as '/'. + /// Any '\\' in the slash path is replaced with the file path separator. + /// The replacements only happen on non-Windows. + fn from_backslash>(s: S) -> Self; + /// Convert the [`OsStr`] backslash path (path separated with '\\') to [`PathBuf`]. + /// + /// Any '\\' in the slash path is replaced with the file path separator. + fn from_backslash_lossy>(s: S) -> Self; + /// Convert the file path into slash path as UTF-8 string. This method is similar to + /// [`Path::to_str`], but the path separator is fixed to '/'. /// - /// On Windows, any non-Unicode sequences are replaced with U+FFFD while the conversion. - /// On non-Windows OS, it is simply equivalent to [`std::path::PathBuf::from`] and there is no - /// loss while conversion. + /// Any file path separators in the file path are replaced with '/'. Only when the replacement + /// happens, heap allocation happens and `Cow::Owned` is returned. + /// When the path contains non-Unicode sequence, this method returns None. /// /// ``` - /// use std::ffi::OsStr; - /// use std::path::PathBuf; - /// use path_slash::PathBufExt; + /// # use std::path::PathBuf; + /// # use std::borrow::Cow; + /// use path_slash::PathBufExt as _; /// - /// let s: &OsStr = "foo/bar/piyo.txt".as_ref(); - /// let p = PathBuf::from_slash_lossy(s); + /// #[cfg(target_os = "windows")] + /// let s = PathBuf::from(r"foo\bar\piyo.txt"); + /// + /// #[cfg(not(target_os = "windows"))] + /// let s = PathBuf::from("foo/bar/piyo.txt"); + /// + /// assert_eq!(s.to_slash(), Some(Cow::Borrowed("foo/bar/piyo.txt"))); + /// ``` + fn to_slash(&self) -> Option>; + /// Convert the file path into slash path as UTF-8 string. This method is similar to + /// [`Path::to_string_lossy`], but the path separator is fixed to '/'. + /// + /// Any file path separators in the file path are replaced with '/'. + /// Any non-Unicode sequences are replaced with U+FFFD. + /// + /// ``` + /// # use std::path::PathBuf; + /// use path_slash::PathBufExt as _; /// /// #[cfg(target_os = "windows")] - /// assert_eq!(p, PathBuf::from(r"foo\bar\piyo.txt")); + /// let s = PathBuf::from(r"foo\bar\piyo.txt"); /// /// #[cfg(not(target_os = "windows"))] - /// assert_eq!(p, PathBuf::from("foo/bar/piyo.txt")); + /// let s = PathBuf::from("foo/bar/piyo.txt"); + /// + /// assert_eq!(s.to_slash_lossy(), "foo/bar/piyo.txt"); /// ``` + fn to_slash_lossy(&self) -> Cow<'_, str>; +} + +impl PathBufExt for PathBuf { #[cfg(not(target_os = "windows"))] + fn from_slash>(s: S) -> Self { + PathBuf::from(s.as_ref()) + } + #[cfg(target_os = "windows")] + fn from_slash>(s: S) -> Self { + str_to_pathbuf(s, '/') + } + + #[cfg(not(target_os = "windows"))] + fn from_slash_lossy>(s: S) -> Self { + PathBuf::from(s.as_ref()) + } + #[cfg(target_os = "windows")] fn from_slash_lossy>(s: S) -> Self { + Self::from_slash(&s.as_ref().to_string_lossy()) + } + + #[cfg(not(target_os = "windows"))] + fn from_backslash>(s: S) -> Self { + str_to_pathbuf(s, '\\') + } + #[cfg(target_os = "windows")] + fn from_backslash>(s: S) -> Self { PathBuf::from(s.as_ref()) } - /// Convert the slash path (path separated with '/') to [`std::path::PathBuf`]. + #[cfg(not(target_os = "windows"))] + fn from_backslash_lossy>(s: S) -> Self { + str_to_pathbuf(&s.as_ref().to_string_lossy(), '\\') + } + #[cfg(target_os = "windows")] + fn from_backslash_lossy>(s: S) -> Self { + PathBuf::from(s.as_ref()) + } + + fn to_slash(&self) -> Option> { + self.as_path().to_slash() + } + + fn to_slash_lossy(&self) -> Cow<'_, str> { + self.as_path().to_slash_lossy() + } +} + +/// Trait to extend [`Cow`]. +/// +/// ``` +/// # use std::borrow::Cow; +/// use path_slash::CowExt as _; +/// +/// assert_eq!( +/// Cow::from_slash("foo/bar/piyo.txt").to_slash_lossy(), +/// "foo/bar/piyo.txt", +/// ); +/// ``` +pub trait CowExt<'a> { + /// Convert the slash path (path separated with '/') to [`Cow`]. /// /// Any '/' in the slash path is replaced with the file path separator. - /// The replacements only happen on Windows since the file path separators on other OSes are the - /// same as '/'. - /// - /// On Windows, any non-Unicode sequences are replaced with U+FFFD while the conversion. - /// On non-Windows OS, it is simply equivalent to [`std::path::PathBuf::from`] and there is no - /// loss while conversion. + /// Heap allocation may only happen on Windows since the file path separators on Unix-like OS + /// are the same as '/'. /// /// ``` - /// use std::ffi::OsStr; - /// use std::path::PathBuf; - /// use path_slash::PathBufExt; + /// # use std::borrow::Cow; + /// # use std::path::Path; + /// use path_slash::CowExt as _; /// - /// let s: &OsStr = "foo/bar/piyo.txt".as_ref(); - /// let p = PathBuf::from_slash_lossy(s); + /// #[cfg(not(target_os = "windows"))] + /// assert_eq!( + /// Cow::from_slash("foo/bar/piyo.txt"), + /// Path::new("foo/bar/piyo.txt"), + /// ); /// /// #[cfg(target_os = "windows")] - /// assert_eq!(p, PathBuf::from(r"foo\bar\piyo.txt")); - /// - /// #[cfg(not(target_os = "windows"))] - /// assert_eq!(p, PathBuf::from("foo/bar/piyo.txt")); + /// assert_eq!( + /// Cow::from_slash("foo/bar/piyo.txt"), + /// Path::new(r"foo\\bar\\piyo.txt"), + /// ); /// ``` - #[cfg(target_os = "windows")] - fn from_slash_lossy>(s: S) -> Self { - Self::from_slash(s.as_ref().to_string_lossy().chars().as_str()) - } - - /// Convert the file path into slash path as UTF-8 string. + fn from_slash(s: &'a str) -> Self; + /// Convert the [`OsStr`] slash path (path separated with '/') to [`Cow`]. /// - /// Any file path separators in the file path is replaced with '/'. - /// Any non-Unicode sequences are replaced with U+FFFD. + /// Any '/' in the slash path is replaced with the file path separator. + /// Heap allocation may only happen on Windows since the file path separators on Unix-like OS + /// are the same as '/'. /// - /// On non-Windows OS, it is equivalent to `to_string_lossy().to_string()` + /// On Windows, any non-Unicode sequences are replaced with U+FFFD while the conversion. + /// On non-Windows OS, there is no loss while conversion. + fn from_slash_lossy(s: &'a OsStr) -> Self; + /// Convert the backslash path (path separated with '\\') to [`Cow`]. + /// + /// Any '\\' in the slash path is replaced with the file path separator. Heap allocation may + /// only happen on non-Windows. /// /// ``` - /// use path_slash::PathBufExt; + /// # use std::borrow::Cow; + /// # use std::path::Path; + /// use path_slash::CowExt as _; + /// + /// #[cfg(not(target_os = "windows"))] + /// assert_eq!( + /// Cow::from_backslash(r"foo\\bar\\piyo.txt"), + /// Path::new("foo/bar/piyo.txt"), + /// ); + /// + /// #[cfg(target_os = "windows")] + /// assert_eq!( + /// Cow::from_backslash(r"foo\\bar\\piyo.txt"), + /// Path::new(r"foo\\bar\\piyo.txt"), + /// ); + /// ``` + fn from_backslash(s: &'a str) -> Self; + /// Convert the [`OsStr`] backslash path (path separated with '\\') to [`Cow`]. + /// + /// Any '\\' in the slash path is replaced with the file path separator. Heap allocation may + /// only happen on non-Windows. + fn from_backslash_lossy(s: &'a OsStr) -> Self; + /// Convert the file path into slash path as UTF-8 string. This method is similar to + /// [`Path::to_str`], but the path separator is fixed to '/'. + /// + /// Any file path separators in the file path are replaced with '/'. Only when the replacement + /// happens, heap allocation happens and `Cow::Owned` is returned. + /// When the path contains non-Unicode sequences, this method returns `None`. + /// + /// ``` + /// # use std::path::Path; + /// # use std::borrow::Cow; + /// use path_slash::CowExt as _; /// /// #[cfg(target_os = "windows")] - /// let s = std::path::PathBuf::from(r"foo\bar\piyo.txt"); + /// let s = Cow::Borrowed(Path::new(r"foo\bar\piyo.txt")); /// /// #[cfg(not(target_os = "windows"))] - /// let s = std::path::PathBuf::from("foo/bar/piyo.txt"); + /// let s = Cow::Borrowed(Path::new("foo/bar/piyo.txt")); /// - /// assert_eq!(s.to_slash_lossy(), "foo/bar/piyo.txt".to_string()); + /// assert_eq!(s.to_slash(), Some(Cow::Borrowed("foo/bar/piyo.txt"))); /// ``` - fn to_slash_lossy(&self) -> String { - self.as_path().to_slash_lossy() - } - - /// Convert the file path into slash path as UTF-8 string. + fn to_slash(&self) -> Option>; + /// Convert the file path into slash path as UTF-8 string. This method is similar to + /// [`Path::to_string_lossy`], but the path separator is fixed to '/'. /// - /// Any file path separators in the file path is replaced with '/'. - /// When the path contains non-Unicode sequence, this method returns None. - /// - /// On non-Windows OS, it is equivalent to `.to_str().map(std::to_string())` + /// Any file path separators in the file path are replaced with '/'. + /// Any non-Unicode sequences are replaced with U+FFFD. /// /// ``` - /// use path_slash::PathBufExt; + /// # use std::path::Path; + /// # use std::borrow::Cow; + /// use path_slash::CowExt as _; /// /// #[cfg(target_os = "windows")] - /// let s = std::path::PathBuf::from(r"foo\bar\piyo.txt"); + /// let s = Cow::Borrowed(Path::new(r"foo\bar\piyo.txt")); /// /// #[cfg(not(target_os = "windows"))] - /// let s = std::path::PathBuf::from("foo/bar/piyo.txt"); + /// let s = Cow::Borrowed(Path::new("foo/bar/piyo.txt")); /// - /// assert_eq!(s.to_slash(), Some("foo/bar/piyo.txt".to_string())); + /// assert_eq!(s.to_slash_lossy(), "foo/bar/piyo.txt"); /// ``` - fn to_slash(&self) -> Option { - self.as_path().to_slash() - } + fn to_slash_lossy(&self) -> Cow<'_, str>; } -#[cfg(test)] -#[macro_use] -extern crate lazy_static; +impl<'a> CowExt<'a> for Cow<'a, Path> { + #[cfg(not(target_os = "windows"))] + fn from_slash(s: &'a str) -> Self { + Cow::Borrowed(Path::new(s)) + } + #[cfg(target_os = "windows")] + fn from_slash(s: &'a str) -> Self { + str_to_path(s, '/') + } + + #[cfg(not(target_os = "windows"))] + fn from_slash_lossy(s: &'a OsStr) -> Self { + Cow::Borrowed(Path::new(s)) + } + #[cfg(target_os = "windows")] + fn from_slash_lossy(s: &'a OsStr) -> Self { + match s.to_string_lossy() { + Cow::Borrowed(s) => str_to_path(s, '/'), + Cow::Owned(s) => Cow::Owned(str_to_pathbuf(&s, '/')), + } + } + + #[cfg(not(target_os = "windows"))] + fn from_backslash(s: &'a str) -> Self { + str_to_path(s, '\\') + } + #[cfg(target_os = "windows")] + fn from_backslash(s: &'a str) -> Self { + Cow::Borrowed(Path::new(s)) + } + + #[cfg(not(target_os = "windows"))] + fn from_backslash_lossy(s: &'a OsStr) -> Self { + match s.to_string_lossy() { + Cow::Borrowed(s) => str_to_path(s, '\\'), + Cow::Owned(s) => Cow::Owned(str_to_pathbuf(&s, '\\')), + } + } + #[cfg(target_os = "windows")] + fn from_backslash_lossy(s: &'a OsStr) -> Self { + Cow::Borrowed(Path::new(s)) + } + + fn to_slash(&self) -> Option> { + self.as_ref().to_slash() + } -#[cfg(test)] -mod test; + fn to_slash_lossy(&self) -> Cow<'_, str> { + self.as_ref().to_slash_lossy() + } +} diff -Nru rust-path-slash-0.1.4/src/test.rs rust-path-slash-0.2.1/src/test.rs --- rust-path-slash-0.1.4/src/test.rs 2021-01-15 14:25:35.000000000 +0000 +++ rust-path-slash-0.2.1/src/test.rs 1970-01-01 00:00:00.000000000 +0000 @@ -1,246 +0,0 @@ -use super::*; -use std::ffi::OsStr; -use std::path; - -lazy_static! { - static ref FROM_SLASH_TESTS: Vec<(String, PathBuf)> = { - [ - ("", ""), - ("/", "/"), - ("//", "/"), - ("foo", "foo"), - ("/foo", "/foo"), - ("foo/", "foo"), - ("/foo/", "/foo"), - ("./foo", "./foo"), - ("../foo", "../foo"), - ("foo/.", "foo/."), - ("foo/..", "foo/.."), - ("foo/bar", "foo/bar"), - ("foo//bar", "foo/bar"), - ("foo/../bar", "foo/../bar"), - ("foo/./bar", "foo/./bar"), - ] - .iter() - .map(|item| { - let (input, expected) = item; - let expected = if cfg!(target_os = "windows") { - let s = expected - .chars() - .map(|c| match c { - '/' => path::MAIN_SEPARATOR, - _ => c, - }) - .collect::(); - PathBuf::from(s) - } else { - PathBuf::from(expected) - }; - (input.to_string(), expected) - }) - .collect::>() - }; -} - -#[test] -fn from_slash() { - for (input, expected) in FROM_SLASH_TESTS.iter() { - assert_eq!(&PathBuf::from_slash(input), expected); - } -} - -#[test] -fn from_slash_lossy() { - for (input, expected) in FROM_SLASH_TESTS.iter() { - let input: &OsStr = input.as_ref(); - assert_eq!(&PathBuf::from_slash_lossy(input), expected); - } -} - -lazy_static! { - static ref TO_SLASH_TESTS: Vec<(PathBuf, String)> = { - [ - "", - "/", - "foo", - "/foo", - "foo", - "/foo", - "./foo", - "../foo", - "foo/..", - "foo/bar", - "foo/../bar", - ] - .iter() - .map(|expected| { - let input = if cfg!(target_os = "windows") { - let s = expected - .chars() - .map(|c| match c { - '/' => path::MAIN_SEPARATOR, - _ => c, - }) - .collect::(); - PathBuf::from(s) - } else { - PathBuf::from(expected) - }; - (input, expected.to_string()) - }) - .collect::>() - }; -} - -#[test] -fn to_slash_path() { - for (input, expected) in TO_SLASH_TESTS.iter() { - assert_eq!(input.as_path().to_slash(), Some(expected.clone())); - } -} - -#[test] -fn to_slash_pathbuf() { - for (input, expected) in TO_SLASH_TESTS.iter() { - assert_eq!(input.to_slash(), Some(expected.clone())); - } -} - -#[test] -fn to_slash_lossy_path() { - for (input, expected) in TO_SLASH_TESTS.iter() { - assert_eq!(&input.as_path().to_slash_lossy(), expected); - } -} - -#[test] -fn to_slash_lossy_pathbuf() { - for (input, expected) in TO_SLASH_TESTS.iter() { - assert_eq!(&input.to_slash_lossy(), expected); - } -} - -#[test] -fn from_slash_to_slash() { - for (_, path) in TO_SLASH_TESTS.iter() { - assert_eq!(PathBuf::from_slash(path).to_slash(), Some(path.clone())); - } -} - -#[cfg(target_os = "windows")] -mod windows { - use super::*; - use std::path::PathBuf; - - #[test] - fn with_driver_letter_to_slash() { - let path = PathBuf::from_slash("C:/foo/bar"); - assert_eq!(path, PathBuf::from(r"C:\foo\bar")); - let slash = path.to_slash(); - assert_eq!(slash, Some("C:/foo/bar".to_string())); - } - - #[test] - fn with_drive_letter_to_slash_lossy() { - let path = PathBuf::from_slash("C:/foo/bar"); - assert_eq!(path, PathBuf::from(r"C:\foo\bar")); - let slash = path.to_slash_lossy(); - assert_eq!(slash, "C:/foo/bar".to_string()); - } - - #[test] - fn with_drive_letter_but_no_path_to_slash() { - let path = PathBuf::from_slash("C:"); - assert_eq!(path, PathBuf::from(r"C:")); - let slash = path.to_slash().unwrap(); - assert_eq!(slash, "C:".to_string()); - } - - #[test] - fn with_drive_letter_but_no_path_to_slash_lossy() { - let path = PathBuf::from_slash("C:"); - assert_eq!(path, PathBuf::from(r"C:")); - let slash = path.to_slash_lossy(); - assert_eq!(slash, "C:".to_string()); - } - - #[test] - fn with_verbatim_drive_letter_to_slash() { - let path = PathBuf::from_slash(r"\\?\C:/foo/bar"); - assert_eq!(path, PathBuf::from(r"\\?\C:\foo\bar")); - let slash = path.to_slash().unwrap(); - assert_eq!(slash, r"\\?\C:/foo/bar".to_string()); - } - - #[test] - fn with_verbatim_drive_letter_to_slash_lossy() { - let path = PathBuf::from_slash(r"\\?\C:/foo/bar"); - assert_eq!(path, PathBuf::from(r"\\?\C:\foo\bar")); - let slash = path.to_slash_lossy(); - assert_eq!(slash, r"\\?\C:/foo/bar".to_string()); - } - - #[test] - fn with_unc_prefix_to_slash() { - let path = PathBuf::from_slash(r"\\server\share/foo/bar"); - assert_eq!(path, PathBuf::from(r"\\server\share\foo\bar")); - let slash = path.to_slash().unwrap(); - assert_eq!(slash, r"\\server\share/foo/bar".to_string()); - } - - #[test] - fn with_unc_prefix_to_slash_lossy() { - let path = PathBuf::from_slash(r"\\server\share/foo/bar"); - assert_eq!(path, PathBuf::from(r"\\server\share\foo\bar")); - let slash = path.to_slash_lossy(); - assert_eq!(slash, r"\\server\share/foo/bar".to_string()); - } - - #[test] - fn with_unc_prefix_but_no_path_to_slash() { - let path = PathBuf::from_slash(r"\\server\share"); - assert_eq!(path, PathBuf::from(r"\\server\share")); - let slash = path.to_slash().unwrap(); - assert_eq!(slash, r"\\server\share".to_string()); - } - - #[test] - fn with_unc_prefix_but_no_path_to_slash_lossy() { - let path = PathBuf::from_slash(r"\\server\share"); - assert_eq!(path, PathBuf::from(r"\\server\share")); - let slash = path.to_slash_lossy(); - assert_eq!(slash, r"\\server\share".to_string()); - } - - #[test] - fn with_verbatim_unc_prefix_to_slash() { - let path = PathBuf::from_slash(r"\\?\UNC\server\share/foo/bar"); - assert_eq!(path, PathBuf::from(r"\\?\UNC\server\share\foo\bar")); - let slash = path.to_slash().unwrap(); - assert_eq!(slash, r"\\?\UNC\server\share/foo/bar".to_string()); - } - - #[test] - fn with_verbatim_unc_prefix_to_slash_lossy() { - let path = PathBuf::from_slash(r"\\?\UNC\server\share/foo/bar"); - assert_eq!(path, PathBuf::from(r"\\?\UNC\server\share\foo\bar")); - let slash = path.to_slash_lossy(); - assert_eq!(slash, r"\\?\UNC\server\share/foo/bar".to_string()); - } - - #[test] - fn with_verbatim_unc_prefix_but_no_path_to_slash() { - let path = PathBuf::from_slash(r"\\?\UNC\server\share"); - assert_eq!(path, PathBuf::from(r"\\?\UNC\server\share")); - let slash = path.to_slash().unwrap(); - assert_eq!(slash, r"\\?\UNC\server\share".to_string()); - } - - #[test] - fn with_verbatim_unc_prefix_but_no_path_to_slash_lossy() { - let path = PathBuf::from_slash(r"\\?\UNC\server\share"); - assert_eq!(path, PathBuf::from(r"\\?\UNC\server\share")); - let slash = path.to_slash_lossy(); - assert_eq!(slash, r"\\?\UNC\server\share".to_string()); - } -} diff -Nru rust-path-slash-0.1.4/tests/lib.rs rust-path-slash-0.2.1/tests/lib.rs --- rust-path-slash-0.1.4/tests/lib.rs 1970-01-01 00:00:00.000000000 +0000 +++ rust-path-slash-0.2.1/tests/lib.rs 1973-11-29 21:33:09.000000000 +0000 @@ -0,0 +1,208 @@ +use lazy_static::lazy_static; +use path_slash::{CowExt as _, PathBufExt as _, PathExt as _}; +use std::borrow::Cow; +use std::ffi::OsStr; +use std::path::{PathBuf, MAIN_SEPARATOR}; + +lazy_static! { + static ref FROM_SLASH_TESTS: Vec<(String, PathBuf)> = { + [ + ("", ""), + ("/", "/"), + ("//", "/"), + ("foo", "foo"), + ("/foo", "/foo"), + ("foo/", "foo/"), + ("/foo/", "/foo/"), + ("./foo", "./foo"), + ("../foo", "../foo"), + ("foo/.", "foo/."), + ("foo/..", "foo/.."), + ("foo/bar", "foo/bar"), + ("foo//bar", "foo/bar"), + ("foo/../bar", "foo/../bar"), + ("foo/./bar", "foo/./bar"), + ("/あ/い/う/え/お", "/あ/い/う/え/お"), + ("あ/い/う/え/お/", "あ/い/う/え/お/"), + ("/あ/い/う/え/お/", "/あ/い/う/え/お/"), + ] + .iter() + .map(|item| { + let (input, expected) = item; + let expected = if cfg!(target_os = "windows") { + let s = expected + .chars() + .map(|c| match c { + '/' => MAIN_SEPARATOR, + _ => c, + }) + .collect::(); + PathBuf::from(s) + } else { + PathBuf::from(expected) + }; + (input.to_string(), expected) + }) + .collect::>() + }; +} + +#[test] +fn from_slash() { + for (input, expected) in FROM_SLASH_TESTS.iter() { + assert_eq!(&PathBuf::from_slash(input), expected); + } +} + +#[test] +fn from_slash_lossy() { + for (input, expected) in FROM_SLASH_TESTS.iter() { + let input: &OsStr = input.as_ref(); + assert_eq!(&PathBuf::from_slash_lossy(input), expected); + } +} + +#[test] +fn from_backslash() { + for (input, expected) in FROM_SLASH_TESTS.iter() { + let input = input.replace('/', r"\"); + assert_eq!(&PathBuf::from_backslash(input), expected); + } +} + +#[test] +fn from_backslash_lossy() { + for (input, expected) in FROM_SLASH_TESTS.iter() { + let input = input.replace('/', r"\"); + let input: &OsStr = input.as_ref(); + assert_eq!(&PathBuf::from_backslash_lossy(input), expected); + } +} + +#[test] +fn cow_from_slash() { + for (input, expected) in FROM_SLASH_TESTS.iter() { + assert_eq!(&Cow::from_slash(input), expected); + } +} + +#[test] +fn cow_from_slash_lossy() { + for (input, expected) in FROM_SLASH_TESTS.iter() { + let input: &OsStr = input.as_ref(); + assert_eq!(&Cow::from_slash_lossy(input), expected); + } +} + +#[test] +fn cow_from_backslash() { + for (input, expected) in FROM_SLASH_TESTS.iter() { + let input = input.replace('/', r"\"); + assert_eq!(&Cow::from_backslash(&input), expected); + } +} + +#[test] +fn cow_from_backslash_lossy() { + for (input, expected) in FROM_SLASH_TESTS.iter() { + let input = input.replace('/', r"\"); + let input: &OsStr = input.as_ref(); + assert_eq!(&Cow::from_backslash_lossy(input), expected); + } +} + +lazy_static! { + static ref TO_SLASH_TESTS: Vec<(PathBuf, String)> = { + [ + "", + "/", + "foo", + "/foo", + "foo/", + "/foo/", + "./foo", + "../foo", + "foo/..", + "foo/bar", + "foo/../bar", + "あ/い/う/え/お/", + "/あ/い/う/え/お", + "/あ/い/う/え/お/", + ] + .iter() + .map(|expected| { + let input = if cfg!(target_os = "windows") { + let s = expected + .chars() + .map(|c| match c { + '/' => MAIN_SEPARATOR, + _ => c, + }) + .collect::(); + PathBuf::from(s) + } else { + PathBuf::from(expected) + }; + (input, expected.to_string()) + }) + .collect::>() + }; +} + +#[test] +fn to_slash_path() { + for (input, expected) in TO_SLASH_TESTS.iter() { + assert_eq!( + input.as_path().to_slash(), + Some(Cow::Borrowed(expected.as_str())) + ); + } +} + +#[test] +fn to_slash_pathbuf() { + for (input, expected) in TO_SLASH_TESTS.iter() { + assert_eq!(input.to_slash(), Some(Cow::Borrowed(expected.as_str()))); + } +} + +#[test] +fn to_slash_cow() { + for (input, expected) in TO_SLASH_TESTS.iter() { + assert_eq!( + Cow::Borrowed(input.as_path()).to_slash(), + Some(Cow::Borrowed(expected.as_str())) + ); + } +} + +#[test] +fn to_slash_lossy_path() { + for (input, expected) in TO_SLASH_TESTS.iter() { + assert_eq!(&input.as_path().to_slash_lossy(), expected); + } +} + +#[test] +fn to_slash_lossy_pathbuf() { + for (input, expected) in TO_SLASH_TESTS.iter() { + assert_eq!(&input.to_slash_lossy(), expected); + } +} + +#[test] +fn to_slash_lossy_cow() { + for (input, expected) in TO_SLASH_TESTS.iter() { + assert_eq!(&Cow::Borrowed(input.as_path()).to_slash_lossy(), expected); + } +} + +#[test] +fn from_slash_to_slash() { + for (_, path) in TO_SLASH_TESTS.iter() { + assert_eq!( + PathBuf::from_slash(path).to_slash(), + Some(Cow::Borrowed(path.as_str())) + ); + } +} diff -Nru rust-path-slash-0.1.4/tests/unix.rs rust-path-slash-0.2.1/tests/unix.rs --- rust-path-slash-0.1.4/tests/unix.rs 1970-01-01 00:00:00.000000000 +0000 +++ rust-path-slash-0.2.1/tests/unix.rs 1973-11-29 21:33:09.000000000 +0000 @@ -0,0 +1,51 @@ +#![cfg(not(target_os = "windows"))] + +use path_slash::{CowExt as _, PathBufExt as _, PathExt as _}; +use std::borrow::Cow; +use std::ffi::OsStr; +use std::os::unix::ffi::OsStrExt; +use std::path::{Path, PathBuf}; + +const INVALID_UTF8_BACKSLASH: &[(&[u8], &str)] = &[ + (b"aaa\\\xc3", "aaa/\u{FFFD}"), + (b"aaa\\\xc3\\", "aaa/\u{FFFD}/"), +]; + +#[test] +fn invalid_utf8_pathbuf_from_backslash() { + for (b, s) in INVALID_UTF8_BACKSLASH { + let o = OsStr::from_bytes(b); + let p = PathBuf::from_backslash_lossy(o); + assert_eq!(p.to_str().unwrap(), *s, "{:x?}", b); + } +} + +#[test] +fn invalid_utf8_cow_from_backslash() { + for (b, s) in INVALID_UTF8_BACKSLASH { + let o = OsStr::from_bytes(b); + let p = Cow::from_backslash_lossy(o); + assert_eq!(p.to_str().unwrap(), *s, "{:x?}", b); + } +} + +const INVALID_UTF8_TO_SLASH: &[(&[u8], &str)] = &[ + (b"aaa/\xc3", "aaa/\u{FFFD}"), + (b"aaa/\xc3/", "aaa/\u{FFFD}/"), +]; + +#[test] +fn invalid_utf8_to_slash_lossy() { + for (input, output) in INVALID_UTF8_TO_SLASH { + let p = Path::new(OsStr::from_bytes(input)); + assert_eq!(p.to_slash_lossy(), *output, "{:x?}", input); + } +} + +#[test] +fn invalid_utf8_to_slash() { + for (input, _) in INVALID_UTF8_TO_SLASH { + let p = Path::new(OsStr::from_bytes(input)); + assert_eq!(p.to_slash(), None, "{:x?}", input); + } +} diff -Nru rust-path-slash-0.1.4/tests/windows.rs rust-path-slash-0.2.1/tests/windows.rs --- rust-path-slash-0.1.4/tests/windows.rs 1970-01-01 00:00:00.000000000 +0000 +++ rust-path-slash-0.2.1/tests/windows.rs 1973-11-29 21:33:09.000000000 +0000 @@ -0,0 +1,245 @@ +#![cfg(target_os = "windows")] + +use path_slash::{CowExt as _, PathBufExt as _, PathExt as _}; +use std::borrow::Cow; +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; +use std::path::{Path, PathBuf}; + +#[test] +fn with_driver_letter_to_slash() { + let path = PathBuf::from_slash("C:/foo/bar"); + assert_eq!(path, PathBuf::from(r"C:\foo\bar")); + let slash = path.to_slash().unwrap(); + assert_eq!(slash, "C:/foo/bar"); +} + +#[test] +fn with_drive_letter_to_slash_lossy() { + let path = PathBuf::from_slash("C:/foo/bar"); + assert_eq!(path, PathBuf::from(r"C:\foo\bar")); + let slash = path.to_slash_lossy(); + assert_eq!(slash, "C:/foo/bar"); +} + +#[test] +fn with_drive_letter_but_no_path_to_slash() { + let path = PathBuf::from_slash("C:"); + assert_eq!(path, PathBuf::from(r"C:")); + let slash = path.to_slash().unwrap(); + assert_eq!(slash, "C:"); +} + +#[test] +fn with_drive_letter_but_no_path_to_slash_lossy() { + let path = PathBuf::from_slash("C:"); + assert_eq!(path, PathBuf::from(r"C:")); + let slash = path.to_slash_lossy(); + assert_eq!(slash, "C:"); +} + +#[test] +fn with_verbatim_drive_letter_to_slash() { + let path = PathBuf::from_slash(r"\\?\C:/foo/bar"); + assert_eq!(path, PathBuf::from(r"\\?\C:\foo\bar")); + let slash = path.to_slash().unwrap(); + assert_eq!(slash, r"\\?\C:/foo/bar"); +} + +#[test] +fn with_verbatim_drive_letter_to_slash_lossy() { + let path = PathBuf::from_slash(r"\\?\C:/foo/bar"); + assert_eq!(path, PathBuf::from(r"\\?\C:\foo\bar")); + let slash = path.to_slash_lossy(); + assert_eq!(slash, r"\\?\C:/foo/bar"); +} + +#[test] +fn with_unc_prefix_to_slash() { + let path = PathBuf::from_slash(r"\\server\share/foo/bar"); + assert_eq!(path, PathBuf::from(r"\\server\share\foo\bar")); + let slash = path.to_slash().unwrap(); + assert_eq!(slash, r"\\server\share/foo/bar"); +} + +#[test] +fn with_unc_prefix_to_slash_lossy() { + let path = PathBuf::from_slash(r"\\server\share/foo/bar"); + assert_eq!(path, PathBuf::from(r"\\server\share\foo\bar")); + let slash = path.to_slash_lossy(); + assert_eq!(slash, r"\\server\share/foo/bar"); +} + +#[test] +fn with_unc_prefix_but_no_path_to_slash() { + let path = PathBuf::from_slash(r"\\server\share"); + assert_eq!(path, PathBuf::from(r"\\server\share")); + let slash = path.to_slash().unwrap(); + assert_eq!(slash, r"\\server\share"); +} + +#[test] +fn with_unc_prefix_but_no_path_to_slash_lossy() { + let path = PathBuf::from_slash(r"\\server\share"); + assert_eq!(path, PathBuf::from(r"\\server\share")); + let slash = path.to_slash_lossy(); + assert_eq!(slash, r"\\server\share"); +} + +#[test] +fn with_verbatim_unc_prefix_to_slash() { + let path = PathBuf::from_slash(r"\\?\UNC\server\share/foo/bar"); + assert_eq!(path, PathBuf::from(r"\\?\UNC\server\share\foo\bar")); + let slash = path.to_slash().unwrap(); + assert_eq!(slash, r"\\?\UNC\server\share/foo/bar"); +} + +#[test] +fn with_verbatim_unc_prefix_to_slash_lossy() { + let path = PathBuf::from_slash(r"\\?\UNC\server\share/foo/bar"); + assert_eq!(path, PathBuf::from(r"\\?\UNC\server\share\foo\bar")); + let slash = path.to_slash_lossy(); + assert_eq!(slash, r"\\?\UNC\server\share/foo/bar"); +} + +#[test] +fn with_verbatim_unc_prefix_but_no_path_to_slash() { + let path = PathBuf::from_slash(r"\\?\UNC\server\share"); + assert_eq!(path, PathBuf::from(r"\\?\UNC\server\share")); + let slash = path.to_slash().unwrap(); + assert_eq!(slash, r"\\?\UNC\server\share"); +} + +#[test] +fn with_verbatim_unc_prefix_but_no_path_to_slash_lossy() { + let path = PathBuf::from_slash(r"\\?\UNC\server\share"); + assert_eq!(path, PathBuf::from(r"\\?\UNC\server\share")); + let slash = path.to_slash_lossy(); + assert_eq!(slash, r"\\?\UNC\server\share"); +} + +const UTF16_TEST_TO_SLASH: &[(&[u16], &str)] = &[ + ( + // あ\い\う\え\お + &[ + 0x3042, 0x005c, 0x3044, 0x005c, 0x3046, 0x005c, 0x3048, 0x005c, 0x304a, + ], + "あ/い/う/え/お", + ), + ( + // あ\い\う\え\お\ + &[ + 0x3042, 0x005c, 0x3044, 0x005c, 0x3046, 0x005c, 0x3048, 0x005c, 0x304a, 0x005c, + ], + "あ/い/う/え/お/", + ), +]; + +#[test] +fn utf16_encoded_os_str_to_slash() { + for (b, s) in UTF16_TEST_TO_SLASH { + let p = PathBuf::from(OsString::from_wide(b)); + assert_eq!(p.to_slash().unwrap(), *s); + } +} + +#[test] +fn utf16_encoded_os_str_to_slash_lossy() { + for (b, s) in UTF16_TEST_TO_SLASH { + let p = PathBuf::from(OsString::from_wide(b)); + assert_eq!(p.to_slash_lossy(), *s); + } +} + +const UTF16_TEST_FROM_SLASH: &[(&[u16], &str)] = &[ + ( + // あ/い/う/え/お + &[ + 0x3042, 0x002f, 0x3044, 0x002f, 0x3046, 0x002f, 0x3048, 0x002f, 0x304a, + ], + r"あ\い\う\え\お", + ), + ( + // あ/い/う/え/お/ + &[ + 0x3042, 0x002f, 0x3044, 0x002f, 0x3046, 0x002f, 0x3048, 0x002f, 0x304a, 0x002f, + ], + r"あ\い\う\え\お\", + ), +]; + +#[test] +fn utf16_encoded_os_str_pathbuf_from_slash() { + for (s, b) in UTF16_TEST_FROM_SLASH { + let o = OsString::from_wide(s); + let p = PathBuf::from_slash_lossy(&o); + assert_eq!(p, Path::new(b), "{:x?}", b); + } +} + +#[test] +fn utf16_encoded_os_str_cow_from_slash() { + for (s, b) in UTF16_TEST_FROM_SLASH { + let o = OsString::from_wide(s); + let p = Cow::from_slash_lossy(&o); + assert_eq!(p, Path::new(b), "{:x?}", b); + } +} + +const INVALID_UTF16_TO_SLASH: &[(&[u16], &str)] = &[ + ( + &[b'a' as u16, b'\\' as u16, 0xd800, b'b' as u16], + "a/\u{FFFD}b", + ), + ( + &[b'a' as u16, b'\\' as u16, 0xd800, b'b' as u16, b'\\' as u16], + "a/\u{FFFD}b/", + ), +]; + +#[test] +fn invalid_utf16_seq_to_slash_lossy() { + for (b, s) in INVALID_UTF16_TO_SLASH { + let o = OsString::from_wide(b); + let p = Path::new(&o); + assert_eq!(p.to_slash_lossy(), *s, "{:x?}", b); + } +} + +#[test] +fn invalid_utf16_seq_to_slash() { + for (b, _) in INVALID_UTF16_TO_SLASH { + let o = OsString::from_wide(b); + let p = Path::new(&o); + assert_eq!(p.to_slash(), None, "{:x?}", b); + } +} + +const INVALID_UTF16_FROM_SLASH: &[(&[u16], &str)] = &[ + ( + &[b'a' as u16, b'/' as u16, 0xd800, b'b' as u16], + "a\\\u{FFFD}b", + ), + ( + &[b'a' as u16, b'/' as u16, 0xd800, b'b' as u16, b'/' as u16], + "a\\\u{FFFD}b\\", + ), +]; + +#[test] +fn invalid_utf16_seq_pathbuf_from_slash() { + for (b, s) in INVALID_UTF16_FROM_SLASH { + let o = OsString::from_wide(b); + let p = PathBuf::from_slash_lossy(&o); + assert_eq!(p.to_str().unwrap(), *s, "{:x?}", b); + } +} + +#[test] +fn invalid_utf16_seq_cow_from_slash() { + for (b, s) in INVALID_UTF16_FROM_SLASH { + let o = OsString::from_wide(b); + let p = Cow::from_slash_lossy(&o); + assert_eq!(p.to_str().unwrap(), *s, "{:x?}", b); + } +}