zoxide/src/util.rs

156 lines
4.1 KiB
Rust

use crate::config;
use crate::db::DB;
use crate::dir::{Dir, Epoch};
use crate::error::SilentExit;
use anyhow::{anyhow, bail, Context, Result};
use std::cmp::Reverse;
use std::io::{Read, Write};
use std::path::Path;
use std::process::{Command, Stdio};
use std::time::SystemTime;
#[cfg(unix)]
pub fn path_to_bytes<P: AsRef<Path>>(path: &P) -> Result<&[u8]> {
use std::os::unix::ffi::OsStrExt;
Ok(path.as_ref().as_os_str().as_bytes())
}
#[cfg(not(unix))]
pub fn path_to_bytes<P: AsRef<Path>>(path: &P) -> Result<&[u8]> {
match path.as_ref().to_str() {
Some(path_str) => Ok(path_str.as_bytes()),
None => bail!("invalid Unicode in path"),
}
}
#[cfg(unix)]
pub fn bytes_to_path(bytes: &[u8]) -> Result<&Path> {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
Ok(Path::new(OsStr::from_bytes(bytes)))
}
#[cfg(not(unix))]
pub fn bytes_to_path(bytes: &[u8]) -> Result<&Path> {
use std::str;
str::from_utf8(bytes)
.map(Path::new)
.context("invalid Unicode in path")
}
pub fn get_db() -> Result<DB> {
let mut db_path = config::zo_data_dir()?;
db_path.push("db.zo");
// FIXME: fallback to old database location; remove in next breaking version
if !db_path.is_file() {
if let Some(mut old_db_path) = dirs::home_dir() {
old_db_path.push(".zo");
if old_db_path.is_file() {
return DB::open_and_migrate(old_db_path, db_path);
}
}
}
DB::open(db_path)
}
pub fn get_current_time() -> Result<Epoch> {
let current_time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.context("system clock set to invalid time")?
.as_secs();
Ok(current_time as Epoch)
}
pub fn fzf_helper<'a, I>(now: Epoch, dirs: I) -> Result<Option<Vec<u8>>>
where
I: IntoIterator<Item = &'a Dir>,
{
let mut fzf = Command::new("fzf")
.args(&["-n2..", "--no-sort"])
.args(config::zo_fzf_extra_args()?)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.context("could not launch fzf")?;
let fzf_stdin = fzf
.stdin
.as_mut()
.ok_or_else(|| anyhow!("could not connect to fzf stdin"))?;
let mut dir_frecencies = dirs
.into_iter()
.map(|dir| (dir, clamp(dir.get_frecency(now), 0.0, 9999.0) as i32))
.collect::<Vec<_>>();
dir_frecencies.sort_unstable_by_key(|&(dir, frecency)| Reverse((frecency, &dir.path)));
for &(dir, frecency) in dir_frecencies.iter() {
// ensure that frecency fits in 4 characters
if let Ok(path_bytes) = path_to_bytes(&dir.path) {
(|| {
write!(fzf_stdin, "{:>4} ", frecency)?;
fzf_stdin.write_all(path_bytes)?;
writeln!(fzf_stdin)
})()
.context("could not write into fzf stdin")?;
}
}
let fzf_stdout = fzf
.stdout
.as_mut()
.ok_or_else(|| anyhow!("could not connect to fzf stdout"))?;
let mut buffer = Vec::new();
fzf_stdout
.read_to_end(&mut buffer)
.context("could not read from fzf stdout")?;
let status = fzf.wait().context("wait failed on fzf")?;
match status.code() {
// normal exit
Some(0) => match buffer.get(12..buffer.len() - 1) {
Some(path) => Ok(Some(path.to_vec())),
None => bail!("fzf returned invalid output"),
},
// no match
Some(1) => Ok(None),
// error
Some(2) => bail!("fzf returned an error"),
// terminated by a signal
Some(code @ 130) => bail!(SilentExit { code }),
Some(128..=254) | None => bail!("fzf was terminated"),
// unknown
_ => bail!("fzf returned an unknown error"),
}
}
// FIXME: replace with f64::clamp once it is stable <https://github.com/rust-lang/rust/issues/44095>
#[must_use = "method returns a new number and does not mutate the original value"]
#[inline]
pub fn clamp(val: f64, min: f64, max: f64) -> f64 {
assert!(min <= max);
if val > max {
max
} else if val > min {
val
} else {
min
}
}