Allow localhost as server target

This commit is contained in:
Leonard Steppy 2025-02-02 02:23:25 +01:00
parent a83cf1013c
commit b6b389e5a0
7 changed files with 230 additions and 84 deletions

View File

@ -17,10 +17,15 @@ Ergo you should be able to connect to your desired servers via ssh and be able t
### Environment variable setup example for linux ### Environment variable setup example for linux
```bash ```bash
export MSSH_SERVERS="crea:server/creative2,sky:sky,city:city2" export MSSH_SERVERS="crea:server/creative2,sky:sky,city:city2,.:minecraft-server"
export MSSH_EDITOR="kate -b <file>" #<file> is the placeholder for the file name export MSSH_EDITOR="kate -b <file>" #<file> is the placeholder for the file name
``` ```
### localhost as server-target
You may also use `.` to refer to your local minecraft server, without having to open the ssh port. You still have to
define the server directory though.
## Usage ## Usage
For detailed usage please see: For detailed usage please see:
@ -80,4 +85,4 @@ and you will find an executable in `target/release`.
### Unit tests ### Unit tests
In order for the unit tests to pass, you will need `python3` In order for the unit tests to pass, you will need `python3`.

View File

@ -12,7 +12,7 @@ pub struct ServerActions<'a> {
impl Display for ServerActions<'_> { impl Display for ServerActions<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: ({})", self.server.ssh_name, self.working_directory.to_string_lossy())?; write!(f, "{}: ({})", self.server.get_name(), self.working_directory.to_string_lossy())?;
for action in &self.actions { for action in &self.actions {
write!(f, "\n{}", action)?; write!(f, "\n{}", action)?;
} }

View File

@ -49,6 +49,7 @@ fn collect_output(
let output = command.output()?; //pipes stdout and stderr automatically let output = command.output()?; //pipes stdout and stderr automatically
if !output.status.success() { if !output.status.success() {
if let Some(logger) = logger { if let Some(logger) = logger {
log!(logger, error, "{}", String::from_utf8_lossy(&output.stdout));
log!(logger, error, "{}", String::from_utf8_lossy(&output.stderr)); log!(logger, error, "{}", String::from_utf8_lossy(&output.stderr));
} }
Err(output.status)?; Err(output.status)?;

View File

@ -7,14 +7,20 @@ pub struct Logger {
macro_rules! define_log_function { macro_rules! define_log_function {
($name:ident, $level:ident) => { ($name:ident, $level:ident) => {
pub fn $name<S>(&self, message: S) where S: ToString { pub fn $name<S>(&self, message: S)
self.log(LogLevel::$level, message.to_string()); where
} S: ToString,
{
self.log(LogLevel::$level, message.to_string());
}
}; };
} }
impl Logger { impl Logger {
pub fn log<S>(&self, level: LogLevel, message: S) where S: ToString { pub fn log<S>(&self, level: LogLevel, message: S)
where
S: ToString,
{
if level >= self.level { if level >= self.level {
println!("{}", message.to_string()); println!("{}", message.to_string());
} }
@ -38,9 +44,10 @@ macro_rules! log {
($logger:expr, $level:ident, $($args:tt)*) => { ($logger:expr, $level:ident, $($args:tt)*) => {
$logger.$level(format!($($args)*)); $logger.$level(format!($($args)*));
}; };
($logger:expr, $($args:tt)*) => { ($logger:expr, $($args:tt)*) => {{
log!($logger, info, $($args)*); //TODO better use default level with log function instead of assuming info as default use $crate::logger::LogLevel;
} $logger.log(LogLevel::default(), format!($($args)*));
}};
} }
#[cfg(test)] #[cfg(test)]

View File

@ -10,6 +10,7 @@ use crate::command::LogRunnable;
use crate::file::{FileMatcher, FileNameInfo}; use crate::file::{FileMatcher, FileNameInfo};
use crate::logger::{LogLevel, Logger}; use crate::logger::{LogLevel, Logger};
use crate::os_string_builder::ReplaceWithOsStr; use crate::os_string_builder::ReplaceWithOsStr;
use crate::server::ServerAddress;
use clap::{Parser, Subcommand, ValueEnum}; use clap::{Parser, Subcommand, ValueEnum};
use lazy_regex::{lazy_regex, Lazy, Regex}; use lazy_regex::{lazy_regex, Lazy, Regex};
use server::{Server, ServerReference}; use server::{Server, ServerReference};
@ -156,7 +157,7 @@ fn main() -> Result<(), String> {
.servers .servers
.iter() .iter()
.map(|server_reference| { .map(|server_reference| {
let server_name = server_reference.get_name(); let server_name = server_reference.get_identifier();
server_reference server_reference
.clone() .clone()
.try_resolve_lazy(&mut configured_servers) .try_resolve_lazy(&mut configured_servers)
@ -191,12 +192,22 @@ fn main() -> Result<(), String> {
Ok(ServerActions { Ok(ServerActions {
server, server,
actions: { actions: {
let output = ShellCmd::new("ssh") let mut ls_command = match &server.address {
.arg(&server.ssh_name) ServerAddress::Ssh { ssh_address } => {
.arg(osf!("ls ") + &working_directory) let mut cmd = ShellCmd::new("ssh");
cmd.arg(ssh_address).arg(osf!("ls ") + &working_directory);
cmd
}
ServerAddress::Localhost => {
let mut cmd = ShellCmd::new("ls");
cmd.arg(&working_directory);
cmd
}
};
let ls_output = ls_command
.collect_output() .collect_output()
.map_err(|e| format!("failed to query files: {e}"))?; .map_err(|e| format!("failed to query files: {e}"))?;
let output = String::from_utf8_lossy(&output.stdout); let output = String::from_utf8_lossy(&ls_output.stdout);
let mut file_matcher = let mut file_matcher =
FileMatcher::from(file_name.as_ref().unwrap_or(&file_name_info.name)); FileMatcher::from(file_name.as_ref().unwrap_or(&file_name_info.name));
@ -279,37 +290,52 @@ fn main() -> Result<(), String> {
for server_actions in actions { for server_actions in actions {
let server = server_actions.server; let server = server_actions.server;
log!(logger, "Performing actions on {}...", server.ssh_name); log!(logger, "Performing actions on {}...", server.get_name());
for file_action in server_actions.actions { for file_action in server_actions.actions {
match file_action.kind { match file_action.kind {
Action::Add | Action::Replace => { Action::Add | Action::Replace => {
let scp_target = osf!(match &server.address {
ServerAddress::Ssh { ssh_address } => format!("{ssh_address}:"),
ServerAddress::Localhost => "".to_string(),
}) + &server_actions.working_directory;
ShellCmd::new("scp") ShellCmd::new("scp")
.arg(file.clone()) .arg(file.clone())
.arg(osf!(&server.ssh_name) + ":" + &server_actions.working_directory) .arg(scp_target)
.run(&logger) .run(&logger)
.map_err(|e| format!("upload failure: {e}"))?; .map_err(|e| format!("upload failure: {e}"))?;
} }
Action::Delete => { Action::Delete => match &server.address {
ShellCmd::new("ssh") ServerAddress::Ssh { ssh_address } => {
.arg(&server.ssh_name) ShellCmd::new("ssh")
.arg(osf!("cd ") + &server_actions.working_directory + "; rm " + &file_action.file) .arg(ssh_address)
.run(&logger) .arg(osf!("rm ") + server_actions.working_directory.join(&file_action.file))
.map_err(|e| format!("failed to delete old version: {e}"))?; .run(&logger)
} .map_err(|e| format!("failed to delete old version: {e}"))?;
Action::Rename { new_name } => { }
ShellCmd::new("ssh") ServerAddress::Localhost => {
.arg(&server.ssh_name) fs::remove_file(server_actions.working_directory.join(&file_action.file))
.arg( .map_err(|e| format!("failed to delete old version: {e}"))?;
osf!("cd ") }
+ &server_actions.working_directory },
+ "; mv " Action::Rename { new_name } => match &server.address {
+ &file_action.file ServerAddress::Ssh { ssh_address } => {
+ " " ShellCmd::new("ssh")
+ new_name, .arg(ssh_address)
) .arg(
.run(&logger) osf!("mv ")
.map_err(|e| format!("failed to rename: {e}"))?; + server_actions.working_directory.join(&file_action.file)
} + " "
+ server_actions.working_directory.join(&new_name),
)
.run(&logger)
.map_err(|e| format!("failed to rename: {e}"))?;
}
ServerAddress::Localhost => {
let dir = &server_actions.working_directory;
fs::rename(dir.join(&file_action.file), dir.join(&new_name))
.map_err(|e| format!("failed to rename: {e}"))?;
}
},
} }
} }
} }
@ -320,12 +346,25 @@ fn main() -> Result<(), String> {
start_ssh_agent(&logger)?; start_ssh_agent(&logger)?;
require_non_empty_servers(&servers)?; require_non_empty_servers(&servers)?;
for server in servers { for server in servers {
log!(logger, "Running command on '{}'...", server.ssh_name); log!(logger, "Running command on '{}'...", server.get_name());
ShellCmd::new("ssh") match &server.address {
.arg(server.ssh_name) ServerAddress::Ssh { ssh_address } => {
.arg(osf!("cd ") + server.server_directory_path + "; " + &command) ShellCmd::new("ssh")
.run(&logger) .arg(ssh_address)
.map_err(|e| format!("{e}"))?; .arg(osf!("cd ") + server.server_directory_path + "; " + &command)
.run(&logger)
.map_err(|e| format!("{e}"))?;
}
ServerAddress::Localhost => {
let mut command_args =
shell_words::split(&command).map_err(|e| format!("failed to parse command: {e}"))?;
ShellCmd::new(command_args.remove(0))
.args(&command_args)
.current_dir(&server.server_directory_path)
.run(&logger)
.map_err(|e| format!("{e}"))?;
}
}
} }
log!(logger, "Done!"); log!(logger, "Done!");
} }
@ -339,12 +378,8 @@ fn main() -> Result<(), String> {
let download_directory = match download_directory { let download_directory = match download_directory {
Some(download_directory) => download_directory, Some(download_directory) => download_directory,
None => { None => {
let home_dir = homedir::my_home() let home_dir =
.map_err(|e| format!("Failed to determine your home directory: {e}")) get_home_directory().map_err(|e| format!("Can't determine download directory: {e}"))?;
.and_then(|home_dir| {
home_dir.ok_or("Failed to determine your home directory".to_string())
})
.map_err(|e| format!("Can't determine download directory: {e}"))?;
home_dir.join("Downloads") home_dir.join("Downloads")
} }
}; };
@ -355,7 +390,7 @@ fn main() -> Result<(), String> {
fs::create_dir_all(&download_directory) fs::create_dir_all(&download_directory)
.map_err(|e| format!("failed to create working directory: {e}"))?; .map_err(|e| format!("failed to create working directory: {e}"))?;
//make sure file doesn't exist in working directory yet, or will be overridden //make sure file doesn't exist in working directory yet, or it will be overridden
let file_name = file let file_name = file
.file_name() .file_name()
.ok_or("can only edit files, not directories")?; .ok_or("can only edit files, not directories")?;
@ -394,15 +429,19 @@ fn main() -> Result<(), String> {
start_ssh_agent(&logger)?; start_ssh_agent(&logger)?;
for server in servers { for server in servers {
log!(logger, "Downloading file from {}...", server.ssh_name); log!(logger, "Getting file from {}...", server.get_name());
let file_source = osf!(match &server.address {
ServerAddress::Ssh { ssh_address } => format!("{ssh_address}:"),
ServerAddress::Localhost => "".to_string(),
}) + server.server_directory_path.join(&file);
ShellCmd::new("scp") ShellCmd::new("scp")
.arg(osf!(&server.ssh_name) + ":" + server.server_directory_path.join(&file)) .arg(&file_source)
.arg(&download_directory) .arg(&download_directory)
.run(&logger) .run(&logger)
.map_err(|e| format!("download failure: {e}"))?; .map_err(|e| format!("download failure: {e}"))?;
//open file in editor //open file in editor
let mut shell_args = shell_words::split(&editor) let mut editor_command_args = shell_words::split(&editor)
.map_err(|e| format!("failed to parse editor command: {e}"))? .map_err(|e| format!("failed to parse editor command: {e}"))?
.into_iter() .into_iter()
.map(|part| { .map(|part| {
@ -410,16 +449,16 @@ fn main() -> Result<(), String> {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let command = shell_args.remove(0); let command = editor_command_args.remove(0);
ShellCmd::new(command) ShellCmd::new(command)
.args(shell_args) .args(editor_command_args)
.run(&logger) .run(&logger)
.map_err(|e| format!("failed to open file in editor: {e}"))?; .map_err(|e| format!("failed to open file in editor: {e}"))?;
//upload file again //upload file again
ShellCmd::new("scp") ShellCmd::new("scp")
.arg(download_directory.join(file_name)) .arg(download_directory.join(file_name))
.arg(osf!(&server.ssh_name) + ":" + server.server_directory_path.join(&file)) .arg(&file_source)
.run(&logger) .run(&logger)
.map_err(|e| format!("failed to re-upload file: {e}"))?; .map_err(|e| format!("failed to re-upload file: {e}"))?;
} }
@ -431,6 +470,12 @@ fn main() -> Result<(), String> {
Ok(()) Ok(())
} }
fn get_home_directory() -> Result<PathBuf, String> {
homedir::my_home()
.map_err(|e| format!("Failed to determine home directory: {e}"))
.and_then(|home_dir| home_dir.ok_or("Failed to find home directory".to_string()))
}
fn require_non_empty_servers(servers: &[Server]) -> Result<(), String> { fn require_non_empty_servers(servers: &[Server]) -> Result<(), String> {
if servers.is_empty() { if servers.is_empty() {
Err("You did not provide any servers for this operation. Please see --help".to_string()) Err("You did not provide any servers for this operation. Please see --help".to_string())
@ -483,28 +528,32 @@ fn parse_server_configuration(config_str: &str) -> Result<Vec<Server>, String> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::parse_server_configuration; use crate::parse_server_configuration;
use crate::server::Server; use crate::server::{Server, ServerAddress};
use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
#[test] #[test]
fn test_parse_server_configuration() { fn test_parse_server_configuration() {
let servers = let servers =
parse_server_configuration("foo:bar,fizz:buzz/bizz").expect("valid server configuration"); parse_server_configuration("foo:bar,.:fizz/buzz").expect("valid server configuration");
assert_eq!( assert_eq!(
vec![ vec![
Server { Server {
ssh_name: "foo".to_string(), address: ServerAddress::Ssh {
ssh_address: "foo".to_string()
},
server_directory_path: PathBuf::from("bar"), server_directory_path: PathBuf::from("bar"),
}, },
Server { Server {
ssh_name: "fizz".to_string(), address: ServerAddress::Localhost,
server_directory_path: PathBuf::from("buzz/bizz"), server_directory_path: PathBuf::from("fizz/buzz"),
} }
], ],
servers servers
); );
} }
/// When we join an absolute path to a relative path, it becomes a relative path
#[test] #[test]
fn path_experiment() { fn path_experiment() {
let server_dir = PathBuf::from("steptech"); let server_dir = PathBuf::from("steptech");
@ -513,4 +562,12 @@ mod test {
let joined = server_dir.join(upload_dir); let joined = server_dir.join(upload_dir);
assert_eq!(PathBuf::from("/home"), joined); assert_eq!(PathBuf::from("/home"), joined);
} }
#[test]
fn rename_experiment() {
fs::rename("test-ressources/files/test", "test-ressources/files/test1")
.expect("failed to rename test file");
fs::rename("test-ressources/files/test1", "test-ressources/files/test")
.expect("failed to rename test1 file back to test");
}
} }

View File

@ -98,6 +98,9 @@ macro_rules! osf {
use $crate::os_string_builder::OsStringBuilder; use $crate::os_string_builder::OsStringBuilder;
OsStringBuilder::default() OsStringBuilder::default()
}}; }};
($s:literal $(,$arg:tt)*) => {
osf!() + format!($s, $($arg)*)
};
($s:expr) => { ($s:expr) => {
osf!() + $s osf!() + $s
}; };
@ -110,6 +113,10 @@ mod test_builder {
#[test] #[test]
fn test_build() { fn test_build() {
assert_eq!(osf!("foo") + "Bar", "fooBar"); assert_eq!(osf!("foo") + "Bar", "fooBar");
assert_eq!(osf!(PathBuf::from("foo")) + "Bar", "fooBar");
let o = 'o';
assert_eq!(osf!("fo{o}") + "Bar", "fooBar");
assert_eq!(osf!("fo{}", o) + "Bar", "fooBar");
} }
#[test] #[test]

View File

@ -1,3 +1,4 @@
use crate::get_home_directory;
use std::cell::LazyCell; use std::cell::LazyCell;
use std::error::Error; use std::error::Error;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
@ -9,14 +10,14 @@ use std::str::FromStr;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ServerReference { pub enum ServerReference {
Resolved(Server), Resolved(Server),
Name(String), Identifier(String),
} }
impl ServerReference { impl ServerReference {
pub fn get_name(&self) -> &str { pub fn get_identifier(&self) -> &str {
match self { match self {
ServerReference::Resolved(server) => &server.ssh_name, ServerReference::Resolved(server) => server.address.identifier(),
ServerReference::Name(name) => name, ServerReference::Identifier(id) => id,
} }
} }
@ -24,7 +25,7 @@ impl ServerReference {
pub fn resolve(self, configured_servers: &[Server]) -> Option<Server> { pub fn resolve(self, configured_servers: &[Server]) -> Option<Server> {
match self { match self {
ServerReference::Resolved(server) => Some(server), ServerReference::Resolved(server) => Some(server),
ServerReference::Name(name) => Self::resolve_server_name(&name, configured_servers), ServerReference::Identifier(name) => Self::resolve_server_name(&name, configured_servers),
} }
} }
@ -36,7 +37,7 @@ impl ServerReference {
{ {
match self { match self {
ServerReference::Resolved(server) => Some(server), ServerReference::Resolved(server) => Some(server),
ServerReference::Name(name) => Self::resolve_server_name(&name, provider), ServerReference::Identifier(name) => Self::resolve_server_name(&name, provider),
} }
} }
@ -51,17 +52,17 @@ impl ServerReference {
{ {
match self { match self {
ServerReference::Resolved(server) => Ok(Some(server)), ServerReference::Resolved(server) => Ok(Some(server)),
ServerReference::Name(name) => provider ServerReference::Identifier(name) => provider
.as_ref() .as_ref()
.map_err(|e| e.clone()) .map_err(|e| e.clone())
.map(|servers| Self::resolve_server_name(&name, servers)), .map(|servers| Self::resolve_server_name(&name, servers)),
} }
} }
fn resolve_server_name(name: &str, servers: &[Server]) -> Option<Server> { fn resolve_server_name(identifier: &str, servers: &[Server]) -> Option<Server> {
servers servers
.iter() .iter()
.find(|server| server.ssh_name == name) .find(|server| server.address.identifier() == identifier)
.cloned() .cloned()
} }
} }
@ -72,13 +73,13 @@ impl FromStr for ServerReference {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Server::from_str(s) Server::from_str(s)
.map(Self::Resolved) .map(Self::Resolved)
.or_else(|_| Ok(Self::Name(s.to_string()))) .or_else(|_| Ok(Self::Identifier(s.to_string())))
} }
} }
impl PartialEq for ServerReference { impl PartialEq for ServerReference {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.get_name() == other.get_name() self.get_identifier() == other.get_identifier()
} }
} }
@ -86,7 +87,7 @@ impl Eq for ServerReference {}
impl Hash for ServerReference { impl Hash for ServerReference {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.get_name().hash(state); self.get_identifier().hash(state);
} }
} }
@ -94,7 +95,7 @@ impl Display for ServerReference {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
ServerReference::Resolved(server) => write!(f, "{}", server), ServerReference::Resolved(server) => write!(f, "{}", server),
ServerReference::Name(name) => write!(f, "{}", name), ServerReference::Identifier(name) => write!(f, "{}", name),
} }
} }
} }
@ -112,32 +113,95 @@ impl Error for ServerReferenceParseError {}
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Server { pub struct Server {
pub ssh_name: String, pub address: ServerAddress,
pub server_directory_path: PathBuf, pub server_directory_path: PathBuf,
} }
impl Server {
pub fn get_name(&self) -> &str {
match &self.address {
ServerAddress::Ssh { ssh_address } => ssh_address,
ServerAddress::Localhost => "this computer",
}
}
}
impl FromStr for Server { impl FromStr for Server {
type Err = ServerParseError; type Err = ServerParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split_once(':') s.split_once(':')
.ok_or(ServerParseError::MissingServerDirectory) .ok_or(ServerParseError::MissingServerDirectory)
.map(|(name, directory)| Self { .and_then(|(identifier, server_directory)| {
ssh_name: name.to_string(), let address = ServerAddress::from_str(identifier);
server_directory_path: PathBuf::from(directory), let mut server_directory_path = PathBuf::from(server_directory);
if let ServerAddress::Localhost = &address {
let home_directory = get_home_directory()
.map_err(|e| ServerParseError::HomeDirectoryRequired { detail_message: e })?;
server_directory_path = home_directory.join(&server_directory_path);
}
Ok(Self {
address,
server_directory_path,
})
}) })
} }
} }
impl Display for Server { impl Display for Server {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.ssh_name, self.server_directory_path.to_string_lossy()) write!(
f,
"{}{}",
match &self.address {
ServerAddress::Ssh { ssh_address } => format!("{ssh_address}:"),
ServerAddress::Localhost => "".to_string(),
},
self.server_directory_path.to_string_lossy()
)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ServerAddress {
Ssh { ssh_address: String },
Localhost,
}
impl ServerAddress {
pub fn ssh<S>(ssh_address: S) -> Self
where
S: ToString,
{
Self::Ssh {
ssh_address: ssh_address.to_string(),
}
}
pub fn from_str<S>(s: S) -> Self
where
S: ToString,
{
let s = s.to_string();
if s == "." {
Self::Localhost
} else {
Self::ssh(s)
}
}
pub fn identifier(&self) -> &str {
match self {
ServerAddress::Ssh { ssh_address } => ssh_address,
ServerAddress::Localhost => ".",
}
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ServerParseError { pub enum ServerParseError {
MissingServerDirectory, MissingServerDirectory,
HomeDirectoryRequired { detail_message: String },
} }
impl Display for ServerParseError { impl Display for ServerParseError {
@ -150,6 +214,10 @@ impl Display for ServerParseError {
double colon to point to the home directory, e.g: 'lobby:'" double colon to point to the home directory, e.g: 'lobby:'"
) )
} }
ServerParseError::HomeDirectoryRequired { detail_message } => write!(
f,
"localhost requires home directory, but: {detail_message}"
),
} }
} }
} }
@ -158,23 +226,24 @@ impl Error for ServerParseError {}
#[cfg(test)] #[cfg(test)]
mod test_server_reference { mod test_server_reference {
use crate::server::{Server, ServerReference}; use crate::server::{Server, ServerAddress, ServerReference};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
#[test] #[test]
fn test_from_str() { fn test_from_str() {
assert_eq!( assert_eq!(
ServerReference::Name("foo".to_string()), ServerReference::Identifier("foo".to_string()),
ServerReference::from_str("foo").unwrap() ServerReference::from_str("foo").unwrap()
); );
assert_eq!( assert_eq!(
ServerReference::Resolved(Server { ServerReference::Resolved(Server {
ssh_name: "crea".to_string(), address: ServerAddress::Ssh {
ssh_address: "crea".to_string()
},
server_directory_path: PathBuf::from("server/creative2") server_directory_path: PathBuf::from("server/creative2")
}), }),
ServerReference::from_str("crea:server/creative2").unwrap() ServerReference::from_str("crea:server/creative2").unwrap()
); );
} }
} }