use crate::{impl_try_from, is_fifo, CachedInput, Input, Output, Result};
use is_terminal::IsTerminal;
use std::convert::TryFrom;
use std::ffi::{OsStr, OsString};
use std::fmt::{self, Debug, Display};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
#[cfg(feature = "http")]
use {
crate::http::{is_http, try_to_url},
url::Url,
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ClioPath {
pub(crate) path: ClioPathEnum,
pub(crate) atomic: bool,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum InOut {
In,
Out,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) enum ClioPathEnum {
Std(Option<InOut>),
Local(PathBuf),
#[cfg(feature = "http")]
Http(Url),
}
impl ClioPathEnum {
fn new(path: &OsStr, io: Option<InOut>) -> Result<Self> {
#[cfg(feature = "http")]
if is_http(path) {
return Ok(ClioPathEnum::Http(try_to_url(path)?));
}
if path == "-" {
Ok(ClioPathEnum::Std(io))
} else {
Ok(ClioPathEnum::Local(path.into()))
}
}
}
impl ClioPath {
pub fn new<S: AsRef<OsStr>>(path: S) -> Result<Self> {
Ok(ClioPath {
path: ClioPathEnum::new(path.as_ref(), None)?,
atomic: false,
})
}
pub fn std() -> Self {
ClioPath {
path: ClioPathEnum::Std(None),
atomic: false,
}
}
pub fn local(path: PathBuf) -> Self {
ClioPath {
path: ClioPathEnum::Local(path),
atomic: false,
}
}
pub(crate) fn with_direction(self, direction: InOut) -> Self {
ClioPath {
path: match self.path {
ClioPathEnum::Std(_) => ClioPathEnum::Std(Some(direction)),
x => x,
},
atomic: self.atomic,
}
}
pub(crate) fn with_path_mut<F, O>(&mut self, update: F) -> O
where
O: Default,
F: FnOnce(&mut PathBuf) -> O,
{
match &mut self.path {
ClioPathEnum::Std(_) => O::default(),
ClioPathEnum::Local(path) => update(path),
#[cfg(feature = "http")]
ClioPathEnum::Http(url) => {
let mut path = Path::new(url.path()).to_owned();
let r = update(&mut path);
url.set_path(&path.to_string_lossy());
r
}
}
}
pub fn set_file_name<S: AsRef<OsStr>>(&mut self, file_name: S) {
self.with_path_mut(|path| path.set_file_name(file_name))
}
pub fn set_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool {
self.with_path_mut(|path| path.set_extension(extension))
}
pub fn add_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool {
if self.file_name().is_some() && !self.ends_with_slash() {
if let Some(existing) = self.extension() {
let mut existing = existing.to_os_string();
existing.push(".");
existing.push(extension);
self.with_path_mut(|path| path.set_extension(existing))
} else {
self.with_path_mut(|path| path.set_extension(extension))
}
} else {
false
}
}
pub fn push<P: AsRef<Path>>(&mut self, path: P) {
self.with_path_mut(|base| base.push(path))
}
pub fn join<P: AsRef<Path>>(&mut self, path: P) -> Self {
let mut new = self.clone();
new.push(path);
new
}
pub fn is_std(&self) -> bool {
matches!(self.path, ClioPathEnum::Std(_))
}
pub fn is_tty(&self) -> bool {
match self.path {
ClioPathEnum::Std(Some(InOut::In)) => std::io::stdin().is_terminal(),
ClioPathEnum::Std(Some(InOut::Out)) => std::io::stdout().is_terminal(),
ClioPathEnum::Std(None) => {
std::io::stdin().is_terminal() || std::io::stdout().is_terminal()
}
_ => false,
}
}
pub fn is_local(&self) -> bool {
matches!(self.path, ClioPathEnum::Local(_))
}
pub(crate) fn is_fifo(&self) -> bool {
match &self.path {
ClioPathEnum::Local(path) => {
if let Ok(meta) = path.metadata() {
is_fifo(&meta)
} else {
false
}
}
ClioPathEnum::Std(_) => true,
#[cfg(feature = "http")]
ClioPathEnum::Http(_) => false,
}
}
pub fn ends_with_slash(&self) -> bool {
cfg_if::cfg_if! {
if #[cfg(unix)] {
use std::os::unix::ffi::OsStrExt;
self.path().as_os_str().as_bytes().ends_with(b"/")
} else if #[cfg(windows)] {
use std::os::windows::ffi::OsStrExt;
self.path().as_os_str().encode_wide().last() == Some('/' as u16)
} else {
self.path().as_os_str().to_string_lossy().ends_with("/")
}
}
}
pub fn files<P>(self, mut predicate: P) -> Result<Vec<ClioPath>>
where
P: FnMut(&ClioPath) -> bool,
{
if self.is_local() {
let mut result = vec![];
for entry in WalkDir::new(self.path()).follow_links(true) {
let entry = entry?;
if entry.file_type().is_file() {
let path = ClioPath::local(entry.into_path());
if predicate(&path) {
result.push(path);
}
}
}
Ok(result)
} else {
Ok(vec![self])
}
}
pub fn create_with_len(self, size: u64) -> Result<Output> {
Output::maybe_with_len(self, Some(size))
}
pub fn create(self) -> Result<Output> {
Output::maybe_with_len(self, None)
}
pub fn open(self) -> Result<Input> {
Input::new(self)
}
pub fn read_all(self) -> Result<CachedInput> {
CachedInput::new(self)
}
pub fn path(&self) -> &Path {
match &self.path {
ClioPathEnum::Std(None) => Path::new("-"),
ClioPathEnum::Std(Some(InOut::In)) => Path::new("/dev/stdin"),
ClioPathEnum::Std(Some(InOut::Out)) => Path::new("/dev/stdout"),
ClioPathEnum::Local(path) => path.as_path(),
#[cfg(feature = "http")]
ClioPathEnum::Http(url) => Path::new(url.path()),
}
}
pub(crate) fn safe_parent(&self) -> Option<&Path> {
match &self.path {
ClioPathEnum::Local(path) => {
let parent = path.parent()?;
if parent == Path::new("") {
Some(Path::new("."))
} else {
Some(parent)
}
}
_ => None,
}
}
pub fn as_os_str(&self) -> &OsStr {
match &self.path {
ClioPathEnum::Std(_) => OsStr::new("-"),
ClioPathEnum::Local(path) => path.as_os_str(),
#[cfg(feature = "http")]
ClioPathEnum::Http(url) => OsStr::new(url.as_str()),
}
}
pub fn to_os_string(self) -> OsString {
match self.path {
ClioPathEnum::Std(_) => OsStr::new("-").to_os_string(),
ClioPathEnum::Local(path) => path.into_os_string(),
#[cfg(feature = "http")]
ClioPathEnum::Http(url) => OsStr::new(url.as_str()).to_os_string(),
}
}
}
impl Deref for ClioPath {
type Target = Path;
fn deref(&self) -> &Self::Target {
self.path()
}
}
impl_try_from!(ClioPath: Clone);