use crate::{configfiles::ConfigFile, layouts::Layout as ManagedLayout, shlex};
use serde::{
de::{self, Deserializer},
Deserialize,
};
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
use std::{
borrow::Cow,
ffi::{OsStr, OsString},
fmt,
marker::PhantomData,
path::{Path, PathBuf},
time::Duration,
};
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct Config {
pub general: General,
pub applications: Vec<Application>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct General {
#[serde(default, deserialize_with = "deserialize_opt_pathbuf_with_tilde")]
pub working_directory: Option<PathBuf>,
pub workspace: Option<String>,
#[serde(deserialize_with = "deserialize_layout")]
pub layout: Layout,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Layout {
Contents(String),
Managed(String),
Path(PathBuf),
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct Application {
#[serde(deserialize_with = "deserialize_application_command")]
pub command: ApplicationCommand,
#[serde(default, deserialize_with = "deserialize_opt_pathbuf_with_tilde")]
pub working_directory: Option<PathBuf>,
#[serde(default, deserialize_with = "deserialize_opt_exec")]
pub exec: Option<Exec>,
}
#[derive(Deserialize, Debug, Default, Clone, PartialEq, Eq)]
pub struct ApplicationCommand {
pub program: String,
#[serde(default)]
pub args: Vec<String>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Exec {
pub commands: Vec<String>,
#[serde(default = "default_exec_type")]
pub exec_type: ExecType,
#[serde(default = "default_timeout", deserialize_with = "deserialize_duration")]
pub timeout: Duration,
}
fn default_exec_type() -> ExecType {
ExecType::Text
}
fn default_timeout() -> Duration {
Duration::from_secs(5)
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ExecType {
Text,
TextNoReturn,
Keys,
}
struct Phantom<T>(PhantomData<T>);
fn deserialize_application_command<'de, D>(deserializer: D) -> Result<ApplicationCommand, D::Error>
where
D: Deserializer<'de>,
{
impl<'de> de::Visitor<'de> for Phantom<ApplicationCommand> {
type Value = ApplicationCommand;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string, sequence of strings or map")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match shlex::split(value) {
Some(mut v) => {
if v.is_empty() {
Err(de::Error::custom("command can not be empty"))
} else {
Ok(ApplicationCommand {
program: v.remove(0).to_owned(),
args: v.into_iter().map(str::to_owned).collect::<Vec<_>>(),
})
}
}
None => Err(de::Error::custom("command can not be empty")),
}
}
fn visit_seq<S>(self, visitor: S) -> Result<Self::Value, S::Error>
where
S: de::SeqAccess<'de>,
{
let mut v: Vec<String> =
de::Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))?;
if v.is_empty() {
Err(de::Error::custom("command can not be empty"))
} else {
Ok(ApplicationCommand {
program: v.remove(0),
args: v,
})
}
}
fn visit_map<M>(self, visitor: M) -> Result<Self::Value, M::Error>
where
M: de::MapAccess<'de>,
{
de::Deserialize::deserialize(de::value::MapAccessDeserializer::new(visitor))
}
}
deserializer.deserialize_any(Phantom::<ApplicationCommand>(PhantomData))
}
fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
impl<'de> de::Visitor<'de> for Phantom<Duration> {
type Value = Duration;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("integer or map")
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Duration::from_secs(value as u64))
}
fn visit_map<M>(self, visitor: M) -> Result<Self::Value, M::Error>
where
M: de::MapAccess<'de>,
{
de::Deserialize::deserialize(de::value::MapAccessDeserializer::new(visitor))
}
}
deserializer.deserialize_any(Phantom::<Duration>(PhantomData))
}
fn deserialize_exec<'de, D>(deserializer: D) -> Result<Exec, D::Error>
where
D: Deserializer<'de>,
{
impl<'de> de::Visitor<'de> for Phantom<Exec> {
type Value = Exec;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string, sequence of strings or map")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Exec {
commands: vec![value.to_owned()],
exec_type: default_exec_type(),
timeout: default_timeout(),
})
}
fn visit_seq<S>(self, visitor: S) -> Result<Self::Value, S::Error>
where
S: de::SeqAccess<'de>,
{
let v: Vec<String> =
de::Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))?;
if v.is_empty() {
Err(de::Error::custom("commands can not be empty"))
} else {
Ok(Exec {
commands: v,
exec_type: default_exec_type(),
timeout: default_timeout(),
})
}
}
fn visit_map<M>(self, visitor: M) -> Result<Self::Value, M::Error>
where
M: de::MapAccess<'de>,
{
de::Deserialize::deserialize(de::value::MapAccessDeserializer::new(visitor))
}
}
deserializer.deserialize_any(Phantom::<Exec>(PhantomData))
}
fn deserialize_opt_exec<'de, D>(deserializer: D) -> Result<Option<Exec>, D::Error>
where
D: Deserializer<'de>,
{
deserialize_exec(deserializer).map(Some)
}
fn deserialize_layout<'de, D>(deserializer: D) -> Result<Layout, D::Error>
where
D: Deserializer<'de>,
{
impl<'de> de::Visitor<'de> for Phantom<Layout> {
type Value = Layout;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if value.find('{').is_some() {
Ok(Layout::Contents(value.into()))
} else if ManagedLayout::open(value).is_ok() {
Ok(Layout::Managed(value.to_owned()))
} else {
Ok(Layout::Path(tilde(value).into_owned()))
}
}
}
deserializer.deserialize_any(Phantom::<Layout>(PhantomData))
}
fn deserialize_pathbuf_with_tilde<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
where
D: Deserializer<'de>,
{
let pathbuf: PathBuf = de::Deserialize::deserialize(deserializer)?;
Ok(tilde(&pathbuf).into_owned())
}
fn deserialize_opt_pathbuf_with_tilde<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
where
D: Deserializer<'de>,
{
deserialize_pathbuf_with_tilde(deserializer).map(Some)
}
#[doc(hidden)]
fn tilde_with_context<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> Cow<Path>
where
SI: AsRef<Path>,
P: AsRef<Path>,
HD: FnOnce() -> Option<P>,
{
let input_str = input.as_ref();
let bytes = input_str.as_os_str().as_bytes();
if bytes[0] == b'~' {
let input_after_tilde = &bytes[1..];
if input_after_tilde.is_empty() || input_after_tilde[0] == b'/' {
if let Some(hd) = home_dir() {
let mut s = OsString::new();
s.push(hd.as_ref().to_path_buf());
s.push(OsStr::from_bytes(input_after_tilde));
PathBuf::from(s).into()
} else {
input_str.into()
}
} else {
input_str.into()
}
} else {
input_str.into()
}
}
fn tilde<SI: ?Sized>(input: &SI) -> Cow<Path>
where
SI: AsRef<Path>,
{
tilde_with_context(input, dirs_next::home_dir)
}