path-slash-0.2.1/.cargo_vcs_info.json0000644000000001360000000000100131000ustar { "git": { "sha1": "222a3add08b43a424d2d4dbaacf014fc68174063" }, "path_in_vcs": "" }path-slash-0.2.1/.codecov.yaml000064400000000000000000000002050072674642500143010ustar 00000000000000# https://docs.codecov.com/docs/commit-status#disabling-a-status coverage: status: project: off patch: off comment: false path-slash-0.2.1/.github/workflows/ci.yaml000064400000000000000000000040250072674642500165750ustar 00000000000000name: CI on: [push, pull_request] env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full jobs: unit-test: name: unit tests strategy: matrix: os: [ubuntu-latest, macos-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 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 run: cargo test --color always linter: name: clippy and rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install tools run: | rustup set profile minimal rustup update stable rustup default stable rustup component add rustfmt clippy rustup show 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 path-slash-0.2.1/.gitignore000064400000000000000000000000430072674642500137050ustar 00000000000000/target **/*.rs.bk Cargo.lock /doc path-slash-0.2.1/CHANGELOG.md000064400000000000000000000105400072674642500135310ustar 00000000000000 # [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 - Fix documentation (#4) [Changes][v0.1.3] # [0.1.2 (v0.1.2)](https://github.com/rhysd/path-slash/releases/tag/v0.1.2) - 16 Jun 2020 - **Fix:** Root path separator was doubled when the path contains Windows driver letter. For example, when `C:/foo` was given, `to_slash` converted it to `C:\\foo`. In this version it converts it to `C:\foo` correctly. - **Improve:** Remove a redundant allocation at calling `to_slash()` method. [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 path-slash-0.2.1/Cargo.toml0000644000000017060000000000100111020ustar # 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" rust-version = "1.38" name = "path-slash" 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] dev-version = false [dependencies] [dev-dependencies.lazy_static] version = "1" [badges.maintenance] status = "passively-maintained" path-slash-0.2.1/Cargo.toml.orig000064400000000000000000000007610072674642500146130ustar 00000000000000[package] name = "path-slash" version = "0.2.1" authors = ["rhysd "] edition = "2018" description = "Conversion to/from a file path from/to slash path" repository = "https://github.com/rhysd/path-slash" readme = "README.md" license = "MIT" categories = ["filesystem"] keywords = ["path"] rust-version = "1.38" [badges] maintenance = { status = "passively-maintained" } [package.metadata.release] dev-version = false [dependencies] [dev-dependencies] lazy_static = "1" path-slash-0.2.1/LICENSE.txt000064400000000000000000000020530072674642500135430ustar 00000000000000the MIT License Copyright (c) 2018 rhysd 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. path-slash-0.2.1/README.md000064400000000000000000000073220072674642500132030ustar 00000000000000Rust library to convert a file path from/to slash path ====================================================== [![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. 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`, `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) -> Cow<'_, Path>` - `PathBufExt` - `PathBuf::from_slash>(s: S) -> PathBuf` - `PathBuf::from_slash_lossy>(s: S) -> PathBuf` - `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 as _; let p = Path::from_slash("foo/bar/piyo.txt"); // On Windows 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 as _; // On Windows let p = PathBuf::from_slash("foo/bar/piyo.txt"); assert_eq!(p, PathBuf::from(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_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"); } ``` Please read [documents][doc] for more details. ## Installation Add `path-slash` to dependencies: ```toml [dependencies] path-slash = "0.x" ``` ## License [the MIT License](LICENSE.txt) [doc-badge]: https://docs.rs/path-slash/badge.svg [doc]: https://docs.rs/path-slash [crates-io-badge]: https://img.shields.io/crates/v/path-slash.svg [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 path-slash-0.2.1/src/lib.rs000064400000000000000000000452570072674642500136400ustar 00000000000000//! 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 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 as _; //! // Trait for extending std::path::PathBuf //! 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().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().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().unwrap(), //! "foo/bar/piyo.txt", //! ); //! //! // Convert to/from PathBuf //! let p = PathBuf::from_slash("foo/bar/piyo.txt"); //! 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, MAIN_SEPARATOR}; #[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 std::path::Path; /// # use std::borrow::Cow; /// use path_slash::PathExt as _; /// /// assert_eq!( /// Path::new("foo").to_slash(), /// Some(Cow::Borrowed("foo")), /// ); /// ``` pub trait PathExt { /// 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 sequence, this method returns None. /// /// ``` /// # 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"); /// /// #[cfg(not(target_os = "windows"))] /// let s = Path::new("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::Path; /// use path_slash::PathExt as _; /// /// #[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_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) -> Cow<'_, str> { use std::path::Component; let mut buf = String::new(); for c in self.components() { match c { 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; } Component::Normal(s) => buf.push_str(&s.to_string_lossy()), } buf.push('/'); } if !windows::ends_with_main_sep(self) && buf != "/" && buf.ends_with('/') { buf.pop(); // Pop last '/' } Cow::Owned(buf) } #[cfg(not(target_os = "windows"))] fn to_slash(&self) -> Option> { self.to_str().map(Cow::Borrowed) } #[cfg(target_os = "windows")] fn to_slash(&self) -> Option> { use std::path::Component; let mut buf = String::new(); for c in self.components() { match c { 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('/'); } if !windows::ends_with_main_sep(self) && buf != "/" && buf.ends_with('/') { buf.pop(); // Pop last '/' } Some(Cow::Owned(buf)) } } /// Trait to extend [`PathBuf`]. /// /// ``` /// # use std::path::PathBuf; /// use path_slash::PathBufExt as _; /// /// assert_eq!( /// PathBuf::from_slash("foo/bar/piyo.txt").to_slash().unwrap(), /// "foo/bar/piyo.txt", /// ); /// ``` pub trait PathBufExt { /// 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 Unix-like OS are /// the same as '/'. /// /// On non-Windows OS, it is simply equivalent to [`PathBuf::from`]. /// /// ``` /// # use std::path::PathBuf; /// use path_slash::PathBufExt as _; /// /// let p = PathBuf::from_slash("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")); /// ``` 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 Unix-like OS 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 [`PathBuf::from`] and there is no /// loss while conversion. /// /// ``` /// # use std::path::PathBuf; /// # use std::ffi::OsStr; /// 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")] /// assert_eq!(p, PathBuf::from(r"foo\bar\piyo.txt")); /// /// #[cfg(not(target_os = "windows"))] /// assert_eq!(p, PathBuf::from("foo/bar/piyo.txt")); /// ``` 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 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 '/'. /// /// 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::PathBuf; /// # use std::borrow::Cow; /// use path_slash::PathBufExt as _; /// /// #[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")] /// 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_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()) } #[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. /// Heap allocation may only happen on Windows since the file path separators on Unix-like OS /// are the same as '/'. /// /// ``` /// # use std::borrow::Cow; /// # use std::path::Path; /// use path_slash::CowExt as _; /// /// #[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!( /// Cow::from_slash("foo/bar/piyo.txt"), /// Path::new(r"foo\\bar\\piyo.txt"), /// ); /// ``` fn from_slash(s: &'a str) -> Self; /// Convert the [`OsStr`] slash path (path separated with '/') to [`Cow`]. /// /// 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 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 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 = Cow::Borrowed(Path::new(r"foo\bar\piyo.txt")); /// /// #[cfg(not(target_os = "windows"))] /// let s = Cow::Borrowed(Path::new("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::Path; /// # use std::borrow::Cow; /// use path_slash::CowExt as _; /// /// #[cfg(target_os = "windows")] /// let s = Cow::Borrowed(Path::new(r"foo\bar\piyo.txt")); /// /// #[cfg(not(target_os = "windows"))] /// let s = Cow::Borrowed(Path::new("foo/bar/piyo.txt")); /// /// assert_eq!(s.to_slash_lossy(), "foo/bar/piyo.txt"); /// ``` fn to_slash_lossy(&self) -> Cow<'_, str>; } 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() } fn to_slash_lossy(&self) -> Cow<'_, str> { self.as_ref().to_slash_lossy() } } path-slash-0.2.1/tests/lib.rs000064400000000000000000000125150072674642500142020ustar 00000000000000use 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())) ); } } path-slash-0.2.1/tests/unix.rs000064400000000000000000000025400072674642500144140ustar 00000000000000#![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); } } path-slash-0.2.1/tests/windows.rs000064400000000000000000000154550072674642500151340ustar 00000000000000#![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); } }