nreplops_tool/
hosts_files.rs

1// host_files.rs
2// Copyright 2022 Matti Hänninen
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License. You may obtain a copy of
6// the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13// License for the specific language governing permissions and limitations under
14// the License.
15
16use std::{
17  collections::HashMap,
18  env, fs,
19  io::{self, Read},
20  path::{Path, PathBuf},
21};
22
23use serde::Deserialize;
24use serde_with::{serde_as, DisplayFromStr};
25
26use crate::{
27  conn_expr::ConnectionExpr,
28  error::Error,
29  host_options::{HostKey, HostOptions, HostOptionsTable},
30};
31
32pub fn load_default_hosts_files() -> Result<HostOptionsTable, Error> {
33  let mut hosts = HostOptionsTable::default();
34  let ps = matching_config_files("nreplops-hosts.toml")
35    .map_err(Error::FailedToLoadDefaultHostConfig)?;
36  for p in ps.into_iter().rev() {
37    let mut f =
38      fs::File::open(p).map_err(Error::FailedToLoadDefaultHostConfig)?;
39    let mut s = String::new();
40    let _ = f
41      .read_to_string(&mut s)
42      .map_err(Error::FailedToLoadDefaultHostConfig)?;
43    let new_hosts: Hosts = toml::from_str(&s).unwrap();
44    hosts.extend(new_hosts.into_iter().map(|(k, v)| (k, v.into())));
45  }
46  Ok(hosts)
47}
48
49pub type Hosts = HashMap<HostKey, HostOptionsDe>;
50
51#[serde_as]
52#[derive(Debug, Deserialize)]
53pub struct HostOptionsDe {
54  name: Option<String>,
55  #[serde_as(as = "DisplayFromStr")]
56  connection: ConnectionExpr,
57  confirm: Option<bool>,
58}
59
60// `HostOptions` is independent of `HostOptionsDe` and, hence, we prefer
61// implementing `Into`.
62#[allow(clippy::from_over_into)]
63impl Into<HostOptions> for HostOptionsDe {
64  fn into(self) -> HostOptions {
65    HostOptions {
66      name: self.name,
67      conn_expr: self.connection,
68      ask_confirmation: self.confirm,
69    }
70  }
71}
72
73fn matching_config_files<P>(file_name: P) -> Result<Vec<PathBuf>, io::Error>
74where
75  P: AsRef<Path>,
76{
77  let mut found = Vec::new();
78
79  let mut current = PathBuf::from(".").canonicalize()?;
80  loop {
81    let mut p = current.clone();
82    p.push(file_name.as_ref());
83    if p.is_file() {
84      found.push(p);
85    }
86    if !current.pop() {
87      break;
88    }
89  }
90
91  if let Some(dir) = env::var_os("HOME") {
92    let mut p = PathBuf::from(dir);
93    if p.is_absolute() {
94      p.push("Library");
95      p.push("Application Support");
96      p.push("nreplops");
97      p.push(file_name.as_ref());
98      if p.is_file() {
99        found.push(p);
100      }
101    }
102  }
103
104  if let Some(dir) = env::var_os("XDG_CONFIG_HOME") {
105    let mut p = PathBuf::from(dir);
106    if p.is_absolute() {
107      p.push("nreplops");
108      p.push(file_name.as_ref());
109      if p.is_file() {
110        found.push(p);
111      }
112    }
113  }
114
115  if let Some(dir) = env::var_os("HOME") {
116    let mut p = PathBuf::from(dir);
117    if p.is_absolute() {
118      p.push(".nreplops");
119      p.push(file_name.as_ref());
120      if p.is_file() {
121        found.push(p);
122      }
123    }
124  }
125
126  Ok(found)
127}