Skip to content

Commit df9d744

Browse files
committed
Provide impl TryFrom for OS strings and paths on Windows
1 parent b5365b3 commit df9d744

3 files changed

Lines changed: 114 additions & 30 deletions

File tree

‎.github/workflows/build.yml‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,25 @@ on:
88
jobs:
99
test:
1010
name: Test
11-
runs-on: ubuntu-latest
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
matrix:
14+
os: [ubuntu-latest, windows-latest]
1215
steps:
1316
- uses: actions/checkout@v4
1417
- uses: dtolnay/rust-toolchain@stable
1518
# Make sure we have all the target shells installed.
16-
- run: sudo apt-get install -y bash dash fish zsh
19+
- name: Install shells (Ubuntu)
20+
if: runner.os == 'Linux'
21+
run: sudo apt-get install -y bash dash fish zsh
22+
- name: Install shells (Windows)
23+
if: runner.os == 'Windows'
24+
run: choco install git -y --no-progress # `git` includes `bash`.
1725
# Record shell versions. Almost unbelievably, it's not possible to get the
1826
# version of Dash from `dash` itself, so we skip it here. Since `sh` might
1927
# be `dash`, we also do not try to get its version.
20-
- name: Shell versions
28+
- name: Shell versions (Ubuntu)
29+
if: runner.os == 'Linux'
2130
run: |
2231
for shell in sh dash; do
2332
for path in $(type -ap "$shell"); do
@@ -30,6 +39,17 @@ jobs:
3039
printf "%10s @ %-30q: %s\n" "$shell" "$path" "$version"
3140
done
3241
done
42+
- name: Shell versions (Windows)
43+
if: runner.os == 'Windows'
44+
shell: bash
45+
run: |
46+
for shell in bash; do
47+
if command -v "$shell" &> /dev/null; then
48+
path=$(command -v "$shell")
49+
version=$("$shell" --version | head -n 1)
50+
printf "%10s @ %-30s: %s\n" "$shell" "$path" "$version"
51+
fi
52+
done
3353
# Test in debug mode first.
3454
- run: cargo test
3555
# Test in release mode too, to defend against, for example, use of

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ It can take as input many different string and byte string types:
4949
- [`&str`] and [`String`]
5050
- [`&bstr::BStr`][`bstr::BStr`] and [`bstr::BString`]
5151
- [`&[u8]`][`slice`] and [`Vec<u8>`]
52-
- [`&OsStr`][`OsStr`] and [`OsString`] (on UNIX)
52+
- [`&OsStr`][`OsStr`] and [`OsString`]
5353
- [`&Path`][`Path`] and [`PathBuf`]
5454

5555
and produce output as (or push into) the following types:

‎src/lib.rs‎

Lines changed: 90 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@
3636
doc = include_str!("../README.md")
3737
)]
3838

39-
use std::ffi::{OsStr, OsString};
40-
use std::path::{Path, PathBuf};
41-
4239
mod ascii;
4340
mod bash;
4441
mod fish;
@@ -176,22 +173,6 @@ impl<'a> From<&'a String> for Quotable<'a> {
176173
}
177174
}
178175

179-
#[cfg(unix)]
180-
impl<'a> From<&'a OsStr> for Quotable<'a> {
181-
fn from(source: &'a OsStr) -> Quotable<'a> {
182-
use std::os::unix::ffi::OsStrExt;
183-
source.as_bytes().into()
184-
}
185-
}
186-
187-
#[cfg(unix)]
188-
impl<'a> From<&'a OsString> for Quotable<'a> {
189-
fn from(source: &'a OsString) -> Quotable<'a> {
190-
use std::os::unix::ffi::OsStrExt;
191-
source.as_bytes().into()
192-
}
193-
}
194-
195176
#[cfg(feature = "bstr")]
196177
impl<'a> From<&'a bstr::BStr> for Quotable<'a> {
197178
fn from(source: &'a bstr::BStr) -> Quotable<'a> {
@@ -208,16 +189,99 @@ impl<'a> From<&'a bstr::BString> for Quotable<'a> {
208189
}
209190
}
210191

192+
// ----------------------------------------------------------------------------
193+
194+
/// Unix-specific support.
211195
#[cfg(unix)]
212-
impl<'a> From<&'a Path> for Quotable<'a> {
213-
fn from(source: &'a Path) -> Quotable<'a> {
214-
source.as_os_str().into()
196+
mod platform {
197+
use std::ffi::{OsStr, OsString};
198+
use std::path::{Path, PathBuf};
199+
200+
use super::Quotable;
201+
202+
impl<'a> From<&'a OsStr> for Quotable<'a> {
203+
fn from(source: &'a OsStr) -> Quotable<'a> {
204+
use std::os::unix::ffi::OsStrExt;
205+
source.as_bytes().into()
206+
}
207+
}
208+
209+
impl<'a> From<&'a OsString> for Quotable<'a> {
210+
fn from(source: &'a OsString) -> Quotable<'a> {
211+
use std::os::unix::ffi::OsStrExt;
212+
source.as_bytes().into()
213+
}
214+
}
215+
216+
impl<'a> From<&'a Path> for Quotable<'a> {
217+
fn from(source: &'a Path) -> Quotable<'a> {
218+
source.as_os_str().into()
219+
}
220+
}
221+
222+
impl<'a> From<&'a PathBuf> for Quotable<'a> {
223+
fn from(source: &'a PathBuf) -> Quotable<'a> {
224+
source.as_os_str().into()
225+
}
215226
}
216227
}
217228

218-
#[cfg(unix)]
219-
impl<'a> From<&'a PathBuf> for Quotable<'a> {
220-
fn from(source: &'a PathBuf) -> Quotable<'a> {
221-
source.as_os_str().into()
229+
// ----------------------------------------------------------------------------
230+
231+
#[cfg(windows)]
232+
pub use platform::UnicodeError;
233+
234+
/// Windows-specific support.
235+
#[cfg(windows)]
236+
mod platform {
237+
use std::ffi::{OsStr, OsString};
238+
use std::path::{Path, PathBuf};
239+
240+
use super::Quotable;
241+
242+
/// Error when converting an [`OsStr`]/[`OsString`] that contains invalid
243+
/// Unicode, e.g. unpaired UTF-16 surrogates on Windows.
244+
#[derive(Debug, Clone)]
245+
#[non_exhaustive]
246+
pub struct UnicodeError {}
247+
248+
impl std::error::Error for UnicodeError {}
249+
250+
impl std::fmt::Display for UnicodeError {
251+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252+
write!(f, "invalid Unicode (unpaired UTF-16 surrogates)")
253+
}
254+
}
255+
256+
impl<'a> TryFrom<&'a OsStr> for Quotable<'a> {
257+
type Error = UnicodeError;
258+
259+
fn try_from(source: &'a OsStr) -> Result<Self, Self::Error> {
260+
source.to_str().map(Quotable::Text).ok_or(UnicodeError {})
261+
}
262+
}
263+
264+
impl<'a> TryFrom<&'a OsString> for Quotable<'a> {
265+
type Error = UnicodeError;
266+
267+
fn try_from(source: &'a OsString) -> Result<Self, Self::Error> {
268+
source.as_os_str().try_into()
269+
}
270+
}
271+
272+
impl<'a> TryFrom<&'a Path> for Quotable<'a> {
273+
type Error = UnicodeError;
274+
275+
fn try_from(source: &'a Path) -> Result<Self, Self::Error> {
276+
source.as_os_str().try_into()
277+
}
278+
}
279+
280+
impl<'a> TryFrom<&'a PathBuf> for Quotable<'a> {
281+
type Error = UnicodeError;
282+
283+
fn try_from(source: &'a PathBuf) -> Result<Self, Self::Error> {
284+
source.as_os_str().try_into()
285+
}
222286
}
223287
}

0 commit comments

Comments
 (0)