tmux_layout/config/
loader.rs1use shellexpand::LookupError;
2use std::env::VarError;
3use std::os::unix::prelude::OsStrExt;
4use std::path::{Path, PathBuf};
5use std::{fs, io};
6use thiserror::Error;
7
8use crate::show_warning;
9
10use super::{Config, PartialConfig};
11
12pub fn load_config_at(path: &Path) -> Result<Config, Error> {
13 let partial_config = load_partial_config_at(path)?;
14 let mut config = Config {
15 selected_session: partial_config.selected_session,
16 sessions: partial_config.sessions,
17 windows: partial_config.windows,
18 ..Default::default()
19 };
20
21 for included_path in partial_config.includes.0 {
22 let included_path = shellexpand::full(&included_path)?;
23 let included_path = path
24 .parent()
25 .unwrap()
26 .join(Path::new(included_path.as_ref()));
27
28 let mut included_config = load_config_at(&included_path)?;
29 config.sessions.append(&mut included_config.sessions);
31 config.windows.append(&mut included_config.windows);
32
33 if let Some(select_session) = included_config.selected_session {
35 if config.selected_session.is_none() {
36 config.selected_session = Some(select_session);
37 } else {
38 show_warning(&format!(
39 "ignoring selected session \"{}\" from {:?}",
40 select_session, included_path
41 ))
42 }
43 }
44 }
45 Ok(config)
46}
47
48pub fn load_partial_config_at(path: &Path) -> Result<PartialConfig, Error> {
49 let config_bytes = fs::read(path).map_err(|error| Error::Io {
50 path: path.to_owned(),
51 error,
52 })?;
53
54 match path.extension().map(|s| s.as_bytes()) {
55 Some(b"toml") => {
56 let config_str =
57 std::str::from_utf8(&config_bytes).map_err(|err| Error::ParseError {
58 path: path.to_owned(),
59 message: format!("UTF-8 error: {}", err),
60 })?;
61
62 toml::from_str(config_str).map_err(|err| Error::ParseError {
63 path: path.to_owned(),
64 message: format!("{}", err),
65 })
66 }
67 Some(b"yml") | Some(b"yaml") => {
68 serde_yaml::from_slice(&config_bytes).map_err(|err| Error::ParseError {
69 path: path.to_owned(),
70 message: format!("{}", err),
71 })
72 }
73 _ => Err(Error::UnsupportedFormat),
74 }
75}
76
77pub fn find_default_config_file() -> Option<PathBuf> {
78 const BASENAME: &str = ".tmux-layout";
79 const EXTS: [&str; 3] = ["yaml", "yml", "toml"];
80
81 let current_dir = std::env::current_dir().ok()?;
82 let home_dir = dirs::home_dir()?;
83
84 for dir in &[current_dir, home_dir] {
85 for ext in &EXTS {
86 let file_path = dir.join(format!("{}.{}", BASENAME, ext));
87 if file_path.exists() {
88 return Some(file_path);
89 }
90 }
91 }
92
93 None
94}
95#[derive(Debug, Error)]
96pub enum Error {
97 #[error("failed to load config file at {path:?}: {error}")]
98 Io { path: PathBuf, error: io::Error },
99 #[error("failed to parse config file at {path:?}: {message}")]
100 ParseError { path: PathBuf, message: String },
101 #[error("unsupported config format (supported: YAML, TOML)")]
102 UnsupportedFormat,
103 #[error("variable lookup error: {0}")]
104 LookupError(#[from] LookupError<VarError>),
105}