aboutsummaryrefslogtreecommitdiffstats
path: root/mumctl/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'mumctl/src/main.rs')
-rw-r--r--mumctl/src/main.rs949
1 files changed, 460 insertions, 489 deletions
diff --git a/mumctl/src/main.rs b/mumctl/src/main.rs
index a187a3a..29c9e44 100644
--- a/mumctl/src/main.rs
+++ b/mumctl/src/main.rs
@@ -1,21 +1,13 @@
-use clap::{App, AppSettings, Arg, Shell, SubCommand, ArgMatches};
use colored::Colorize;
-use log::{Record, Level, Metadata, LevelFilter, error, warn};
-use mumlib::command::{Command, CommandResponse};
-use mumlib::config::{self, ServerConfig, Config};
-use mumlib::state::Channel;
-use std::{io::{self, Read, BufRead, Write}, iter, fmt::{Display, Formatter}, os::unix::net::UnixStream};
+use log::*;
+use mumlib::command::{Command as MumCommand, CommandResponse};
+use mumlib::config::{self, Config, ServerConfig};
+use mumlib::state::Channel as MumChannel;
+use std::{fmt,io::{self, BufRead, Read, Write}, iter, os::unix::net::UnixStream};
+use structopt::{clap::Shell, StructOpt};
const INDENTATION: &str = " ";
-macro_rules! error_if_err {
- ($func:expr) => {
- if let Err(e) = $func {
- error!("{}", e);
- }
- };
-}
-
struct SimpleLogger;
impl log::Log for SimpleLogger {
@@ -32,7 +24,8 @@ impl log::Log for SimpleLogger {
Level::Warn => "warning: ".yellow(),
_ => "".normal(),
},
- record.args());
+ record.args()
+ );
}
}
@@ -41,519 +34,499 @@ impl log::Log for SimpleLogger {
static LOGGER: SimpleLogger = SimpleLogger;
-fn main() {
- log::set_logger(&LOGGER)
- .map(|()| log::set_max_level(LevelFilter::Info)).unwrap();
- let mut config = match config::read_default_cfg() {
- Ok(c) => c,
- Err(e) => {
- error!("Couldn't read config: {}", e);
- return;
- }
- };
+#[derive(Debug, StructOpt)]
+struct Mum {
+ #[structopt(subcommand)]
+ command: Command,
+}
- let mut app = App::new("mumctl")
- .setting(AppSettings::ArgRequiredElseHelp)
- .version(env!("VERSION"))
- .subcommand(
- SubCommand::with_name("connect")
- .about("Connect to a server")
- .arg(Arg::with_name("host").required(true))
- .arg(Arg::with_name("username"))
- .arg(Arg::with_name("password"))
- .arg(
- Arg::with_name("port")
- .long("port")
- .short("p")
- .takes_value(true),
- ),
- )
- .subcommand(
- SubCommand::with_name("disconnect")
- .about("Disconnect from the currently connected server"),
- )
- .subcommand(
- SubCommand::with_name("server")
- .setting(AppSettings::ArgRequiredElseHelp)
- .about("Handle servers")
- .subcommand(
- SubCommand::with_name("config")
- .about("Configure a saved server")
- .arg(Arg::with_name("server_name"))
- .arg(Arg::with_name("var_name"))
- .arg(Arg::with_name("var_value")),
- )
- .subcommand(
- SubCommand::with_name("rename")
- .about("Rename a saved server")
- .arg(Arg::with_name("prev_name").required(true))
- .arg(Arg::with_name("next_name").required(true)),
- )
- .subcommand(
- SubCommand::with_name("add")
- .about("Add a new saved server")
- .arg(Arg::with_name("name").required(true))
- .arg(Arg::with_name("host").required(true))
- .arg(
- Arg::with_name("port")
- .long("port")
- .takes_value(true)
- .default_value("64738"),
- )
- .arg(
- Arg::with_name("username")
- .long("username")
- .takes_value(true),
- )
- .arg(
- Arg::with_name("password")
- .long("password")
- .takes_value(true),
- ),
- )
- .subcommand(
- SubCommand::with_name("remove")
- .about("Remove a saved server")
- .arg(Arg::with_name("name").required(true)),
- )
- .subcommand(
- SubCommand::with_name("list")
- .about("List saved servers and number of people connected"),
- ),
- )
- .subcommand(
- SubCommand::with_name("channel")
- .about("Handle channels in the connected server")
- .setting(AppSettings::ArgRequiredElseHelp)
- .subcommand(
- SubCommand::with_name("list")
- .about("List all channels")
- .arg(Arg::with_name("short").long("short").short("s")),
- )
- .subcommand(
- SubCommand::with_name("connect")
- .about("Connect to another channel")
- .arg(Arg::with_name("channel").required(true)),
- ),
- )
- .subcommand(SubCommand::with_name("status").about("Show current status"))
- .subcommand(
- SubCommand::with_name("config")
- .about("Change config values")
- .arg(Arg::with_name("name").required(true))
- .arg(Arg::with_name("value").required(true)),
- )
- .subcommand(
- SubCommand::with_name("config-reload").about("Force a reload of the config file"),
- )
- .subcommand(
- SubCommand::with_name("completions")
- .about("Generate CLI completions")
- .arg(Arg::with_name("zsh").long("zsh"))
- .arg(Arg::with_name("bash").long("bash"))
- .arg(Arg::with_name("fish").long("fish")),
- )
- .subcommand(
- SubCommand::with_name("volume")
- .about("Change volume of either you or someone else")
- .subcommand(
- SubCommand::with_name("set")
- .arg(Arg::with_name("user").required(true))
- .arg(Arg::with_name("volume").required(true)),
- )
- .arg(Arg::with_name("user").required(true))
- .setting(AppSettings::SubcommandsNegateReqs),
- )
- .subcommand(
- SubCommand::with_name("mute")
- .about("Mute someone/yourself")
- .arg(Arg::with_name("user"))
- )
- .subcommand(
- SubCommand::with_name("unmute")
- .about("Unmute someone/yourself")
- .arg(Arg::with_name("user"))
- )
- .subcommand(
- SubCommand::with_name("deafen")
- .about("Deafen yourself")
- )
- .subcommand(
- SubCommand::with_name("undeafen")
- .about("Undeafen yourself")
- );
+#[derive(Debug, StructOpt)]
+enum Command {
+ /// Connect to a server
+ Connect {
+ host: String,
+ username: Option<String>,
+ password: Option<String>,
+ #[structopt(short = "p", long = "port")]
+ port: Option<u16>,
+ },
+ /// Disconnect from the currently connected server
+ Disconnect,
+ /// Handle servers
+ Server(Server),
+ /// Handle channels in the connected server
+ Channel(Channel),
+ /// Show current status
+ Status,
+ /// Change config values
+ Config {
+ key: String,
+ value: String,
+ },
+ /// Reload the config file
+ ConfigReload,
+ /// Output CLI completions
+ Completions(Completions),
+ /// Change volume of either you or someone else
+ Volume {
+ user: String,
+ volume: Option<f32>,
+ },
+ /// Mute someone/yourself
+ Mute {
+ user: Option<String>,
+ },
+ /// Unmute someone/yourself
+ Unmute {
+ user: Option<String>,
+ },
+ /// Deafen yourself
+ Deafen,
+ /// Undeafen yourself
+ Undeafen,
+}
+
+#[derive(Debug, StructOpt)]
+enum Server {
+ /// Configure a saved server
+ Config {
+ server_name: Option<String>,
+ key: Option<String>,
+ value: Option<String>,
+ },
+ /// Rename a saved server
+ Rename {
+ old_name: String,
+ new_name: String
+ },
+ /// Add a new saved server
+ Add {
+ name: String,
+ host: String,
+ #[structopt(short = "p", long = "port")]
+ port: Option<u16>,
+ username: Option<String>,
+ #[structopt(requires = "username")]
+ password: Option<String>,
+ },
+ /// Remove a saved server
+ Remove {
+ name: String,
+ },
+ /// List saved servers and number of people connected
+ List,
+}
- let matches = app.clone().get_matches();
+#[derive(Debug, StructOpt)]
+enum Channel {
+ List {
+ #[structopt(short = "s", long = "short")]
+ short: bool,
+ },
+ Connect {
+ name: String,
+ },
+}
- if let Err(e) = process_matches(matches, &mut config, &mut app) {
- error!("{}", e);
+#[derive(Debug, StructOpt)]
+enum Completions {
+ Zsh,
+ Bash,
+ Fish,
+}
+
+#[derive(Debug)]
+enum CliError {
+ NoUsername,
+ ConnectionError,
+ NoServerFound(String),
+ NotSet(String),
+ UseServerRename,
+ ConfigKeyNotFound(String),
+ ServerAlreadyExists(String),
+ NoServers,
+}
+
+#[derive(Debug)]
+struct Error(Box<dyn std::error::Error>); // new type since otherwise we'd get an impl conflict with impl<T> From<T> for T below
+
+impl<E> From<E> for Error
+where
+ E: std::error::Error + fmt::Display + 'static,
+{
+ fn from(e: E) -> Self {
+ Error(Box::new(e))
}
+}
- if !config::cfg_exists() {
- println!(
- "Config file not found. Create one in {}? [Y/n]",
- config::default_cfg_path().display(),
- );
- let stdin = std::io::stdin();
- let response = stdin.lock().lines().next();
- if let Some(Ok(true)) = response.map(|e| e.map(|e| &e == "Y")) {
- error_if_err!(config.write_default_cfg(true));
- }
- } else {
- error_if_err!(config.write_default_cfg(false));
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&self.0, f)
}
}
-fn process_matches(matches: ArgMatches, config: &mut Config, app: &mut App) -> Result<(), Error> {
- if let Some(matches) = matches.subcommand_matches("connect") {
- match_server_connect(matches, &config)?;
- } else if let Some(_) = matches.subcommand_matches("disconnect") {
- error_if_err!(send_command(Command::ServerDisconnect)?);
- } else if let Some(matches) = matches.subcommand_matches("server") {
- if let Some(matches) = matches.subcommand_matches("config") {
- match_server_config(matches, config);
- } else if let Some(matches) = matches.subcommand_matches("rename") {
- match_server_rename(matches, config);
- } else if let Some(matches) = matches.subcommand_matches("remove") {
- match_server_remove(matches, config);
- } else if let Some(matches) = matches.subcommand_matches("add") {
- match_server_add(matches, config);
- } else if let Some(_) = matches.subcommand_matches("list") {
- if config.servers.is_empty() {
- warn!("No servers in config");
+impl std::error::Error for CliError {}
+
+impl fmt::Display for CliError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ CliError::NoUsername => {
+ write!(f, "No username specified")
}
- let query = config
- .servers
- .iter()
- .map(|e| {
- let response = send_command(Command::ServerStatus {
- host: e.host.clone(),
- port: e.port.unwrap_or(mumlib::DEFAULT_PORT),
- });
- response.map(|f| (e, f))
- })
- .collect::<Result<Vec<_>, _>>()?;
- for (server, response) in query.into_iter()
- .filter(|e| e.1.is_ok())
- .map(|e| (e.0, e.1.unwrap().unwrap())) {
- if let CommandResponse::ServerStatus {
- users, max_users, ..
- } = response
- {
- println!("{} [{}/{}]", server.name, users, max_users)
- } else {
- unreachable!()
- }
+ CliError::ConnectionError => {
+ write!(f, "Unable to connect to mumd. Is mumd running?")
+ }
+ CliError::NoServerFound(s) => {
+ write!(f, "Server '{}' not found", s)
+ }
+ CliError::NotSet(s) => {
+ write!(f, "Key '{}' not set", s)
+ }
+ CliError::UseServerRename => {
+ write!(f, "Use 'server rename' instead")
+ }
+ CliError::ConfigKeyNotFound(s) => {
+ write!(f, "Key '{}' not found", s)
+ }
+ CliError::ServerAlreadyExists(s) => {
+ write!(f, "A server named '{}' already exists", s)
+ }
+ CliError::NoServers => {
+ write!(f, "No servers found")
}
}
- } else if let Some(matches) = matches.subcommand_matches("channel") {
- if let Some(_matches) = matches.subcommand_matches("list") {
- match send_command(Command::ChannelList)? {
- Ok(res) => match res {
- Some(CommandResponse::ChannelList { channels }) => {
- print_channel(&channels, 0);
- }
- _ => unreachable!(),
- },
- Err(e) => error!("{}", e),
+ }
+}
+
+fn main() {
+ log::set_logger(&LOGGER)
+ .map(|()| log::set_max_level(LevelFilter::Info))
+ .unwrap();
+ if let Err(e) = match_opt() {
+ error!("{}", e);
+ }
+}
+fn match_opt() -> Result<(), Error> {
+ let mut config = config::read_default_cfg()?;
+
+ let opt = Mum::from_args();
+ match opt.command {
+ Command::Connect {
+ host,
+ username,
+ password,
+ port,
+ } => {
+ let port = port.unwrap_or(mumlib::DEFAULT_PORT);
+
+ let (host, username, password, port) =
+ match config.servers.iter().find(|e| e.name == host) {
+ Some(server) => (
+ &server.host,
+ server
+ .username
+ .as_ref()
+ .or(username.as_ref())
+ .ok_or(CliError::NoUsername)?,
+ server.password.as_ref().or(password.as_ref()),
+ server.port.unwrap_or(port),
+ ),
+ None => (
+ &host,
+ username.as_ref().ok_or(CliError::NoUsername)?,
+ password.as_ref(),
+ port,
+ ),
+ };
+
+ let response = send_command(MumCommand::ServerConnect {
+ host: host.to_string(),
+ port,
+ username: username.to_string(),
+ password: password.map(|x| x.to_string()),
+ accept_invalid_cert: true, //TODO
+ })??;
+ if let Some(CommandResponse::ServerConnect { welcome_message }) = response {
+ println!("Connected to {}", host);
+ if let Some(message) = welcome_message {
+ println!("Welcome: {}", message);
+ }
}
- } else if let Some(matches) = matches.subcommand_matches("connect") {
- error_if_err!(send_command(Command::ChannelJoin {
- channel_identifier: matches.value_of("channel").unwrap().to_string()
- }));
}
- } else if let Some(_) = matches.subcommand_matches("status") {
- match send_command(Command::Status)? {
- Ok(res) => match res {
- Some(CommandResponse::Status { server_state }) => {
- parse_status(&server_state);
+ Command::Disconnect => {
+ send_command(MumCommand::ServerDisconnect)??;
+ }
+ Command::Server(server_command) => {
+ match_server_command(server_command, &mut config)?;
+ }
+ Command::Channel(channel_command) => {
+ match channel_command {
+ Channel::List { short: _short } => {
+ //TODO actually use this
+ match send_command(MumCommand::ChannelList)?? {
+ Some(CommandResponse::ChannelList { channels }) => {
+ print_channel(&channels, 0);
+ }
+ _ => unreachable!("Response should only be a ChannelList"),
+ }
+ }
+ Channel::Connect { name } => {
+ send_command(MumCommand::ChannelJoin {
+ channel_identifier: name,
+ })??;
}
- _ => unreachable!(),
- },
- Err(e) => error!("{}", e),
+ }
}
- } else if let Some(matches) = matches.subcommand_matches("config") {
- let name = matches.value_of("name").unwrap();
- let value = matches.value_of("value").unwrap();
- match name {
+ Command::Status => match send_command(MumCommand::Status)?? {
+ Some(CommandResponse::Status { server_state }) => {
+ parse_state(&server_state);
+ }
+ _ => unreachable!("Response should only be a Status"),
+ },
+ Command::Config { key, value } => match key.as_str() {
"audio.input_volume" => {
if let Ok(volume) = value.parse() {
- error_if_err!(send_command(Command::InputVolumeSet(volume))?);
+ send_command(MumCommand::InputVolumeSet(volume))??;
config.audio.input_volume = Some(volume);
}
}
"audio.output_volume" => {
if let Ok(volume) = value.parse() {
- error_if_err!(send_command(Command::OutputVolumeSet(volume))?);
+ send_command(MumCommand::OutputVolumeSet(volume))??;
config.audio.output_volume = Some(volume);
}
}
_ => {
- error!("unknown config value {}", name);
+ return Err(CliError::ConfigKeyNotFound(key))?;
}
+ },
+ Command::ConfigReload => {
+ send_command(MumCommand::ConfigReload)??;
}
- } else if matches.subcommand_matches("config-reload").is_some() {
- error_if_err!(send_command(Command::ConfigReload)?);
- } else if let Some(matches) = matches.subcommand_matches("completions") {
- app.gen_completions_to(
- "mumctl",
- match matches.value_of("shell").unwrap_or("zsh") {
- "bash" => Shell::Bash,
- "fish" => Shell::Fish,
- _ => Shell::Zsh,
- },
- &mut io::stdout(),
- );
- } else if let Some(matches) = matches.subcommand_matches("volume") {
- if let Some(matches) = matches.subcommand_matches("set") {
- let user = matches.value_of("user").unwrap();
- let volume = matches.value_of("volume").unwrap();
- if let Ok(val) = volume.parse() {
- error_if_err!(send_command(Command::UserVolumeSet(user.to_string(), val)))
+ Command::Completions(completions) => {
+ Mum::clap().gen_completions_to(
+ "mumctl",
+ match completions {
+ Completions::Bash => Shell::Bash,
+ Completions::Fish => Shell::Fish,
+ _ => Shell::Zsh,
+ },
+ &mut io::stdout(),
+ );
+ }
+ Command::Volume { user, volume } => {
+ if let Some(volume) = volume {
+ send_command(MumCommand::UserVolumeSet(user, volume))??;
} else {
- error!("invalid volume value: {}", volume);
+ //TODO report volume of user
+ // needs work on mumd
+ todo!();
}
- } else {
- let _user = matches.value_of("user").unwrap();
- //TODO implement me
- //needs work on mumd to implement
}
- } else if let Some(matches) = matches.subcommand_matches("mute") {
- let command = if let Some(user) = matches.value_of("user") {
- Command::MuteOther(user.to_string(), Some(true))
- } else {
- Command::MuteSelf(Some(true))
- };
- if let Err(e) = send_command(command)? {
- error!("{}", e);
- }
- } else if let Some(matches) = matches.subcommand_matches("unmute") {
- let command = if let Some(user) = matches.value_of("user") {
- Command::MuteOther(user.to_string(), Some(false))
- } else {
- Command::MuteSelf(Some(false))
- };
- if let Err(e) = send_command(command)? {
- error!("{}", e);
+ Command::Mute { user } => match user {
+ Some(user) => {
+ send_command(MumCommand::MuteOther(user, Some(true)))??;
+ }
+ None => {
+ send_command(MumCommand::MuteSelf(Some(true)))??;
+ }
+ },
+ Command::Unmute { user } => match user {
+ Some(user) => {
+ send_command(MumCommand::MuteOther(user, Some(false)))??;
+ }
+ None => {
+ send_command(MumCommand::MuteSelf(Some(false)))??;
+ }
+ },
+ Command::Deafen => {
+ send_command(MumCommand::DeafenSelf(Some(true)))??;
}
- } else if let Some(_) = matches.subcommand_matches("deafen") {
- if let Err(e) = send_command(Command::DeafenSelf(Some(true)))? {
- error!("{}", e);
+ Command::Undeafen => {
+ send_command(MumCommand::DeafenSelf(Some(false)))??;
}
- } else if let Some(_) = matches.subcommand_matches("undeafen") {
- if let Err(e) = send_command(Command::DeafenSelf(Some(false)))? {
- error!("{}", e);
+ }
+
+ if !config::cfg_exists() {
+ println!(
+ "Config file not found. Create one in {}? [Y/n]",
+ config::default_cfg_path().display(),
+ );
+ let stdin = std::io::stdin();
+ let response = stdin.lock().lines().next();
+ if let Some(Ok(true)) = response.map(|e| e.map(|e| &e == "Y")) {
+ config.write_default_cfg(true)?;
}
} else {
- unreachable!();
+ config.write_default_cfg(false)?;
}
-
Ok(())
}
-fn match_server_connect(matches: &clap::ArgMatches<'_>, config: &mumlib::config::Config) -> Result<(), Error> {
- let host = matches.value_of("host").unwrap();
- let username = matches.value_of("username");
- let password = matches.value_of("password");
- let port = match matches.value_of("port").map(|e| e.parse()) {
- None => Some(mumlib::DEFAULT_PORT),
- Some(Err(_)) => None,
- Some(Ok(v)) => Some(v),
- };
- if let Some(port) = port {
- let (host, port, username, password) = match config.servers.iter().find(|e| e.name == host) {
- Some(server_config) => {
- let host = server_config.host.as_str();
- let port = server_config.port.unwrap_or(port);
- let username = server_config
- .username
- .as_deref()
- .or(username);
- if username.is_none() {
- error!("no username specified");
- return Ok(()); //TODO? return as error
- }
- let password = server_config
- .password
- .as_deref()
- .or(password);
- (host, port, username.unwrap(), password)
- }
- None => {
- if username.is_none() {
- error!("no username specified");
- return Ok(()); //TODO? return as error
- }
- (host, port, username.unwrap(), password)
- }
- };
- let response = send_command(Command::ServerConnect {
- host: host.to_string(),
- port,
- username: username.to_string(),
- password: password.map(|x| x.to_string()),
- accept_invalid_cert: true, //TODO
- })?
- .map(|e| (e, host));
- match response {
- Ok((e, host)) => {
- if let Some(CommandResponse::ServerConnect { welcome_message }) = e {
- println!("Connected to {}", host);
- if let Some(message) = welcome_message {
- println!("Welcome: {}", message);
+fn match_server_command(server_command: Server, config: &mut Config) -> Result<(), CliError> {
+ match server_command {
+ Server::Config {
+ server_name,
+ key,
+ value,
+ } => {
+ let server_name = match server_name {
+ Some(server_name) => server_name,
+ None => {
+ for server in config.servers.iter() {
+ println!("{}", server.name);
}
+ return Ok(());
}
- }
- Err(e) => {
- error!("{}", e);
- }
- };
- }
-
- Ok(())
-}
+ };
+ let server = config
+ .servers
+ .iter_mut()
+ .find(|s| s.name == server_name)
+ .ok_or(CliError::NoServerFound(server_name))?;
-fn match_server_config(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) {
- if let Some(server_name) = matches.value_of("server_name") {
- let server = config.servers.iter_mut().find(|s| s.name == server_name);
- if let Some(server) = server {
- if let Some(var_name) = matches.value_of("var_name") {
- if let Some(var_value) = matches.value_of("var_value") {
- // save var_value in var_name (if it is valid)
- match var_name {
- "name" => {
- error!("use mumctl server rename instead!");
- }
- "host" => {
- server.host = var_value.to_string();
- }
- "port" => {
- server.port = Some(var_value.parse().unwrap());
- }
- "username" => {
- server.username = Some(var_value.to_string());
- }
- "password" => {
- server.password = Some(var_value.to_string()); //TODO ask stdin if empty
- }
- _ => {
- error!("variable {} not found", var_name);
- }
- };
- } else {
- // var_value is None
- // print value of var_name
+ match (key.as_deref(), value) {
+ (None, _) => {
+ print!(
+ "{}{}{}{}",
+ format!("host: {}\n", server.host.to_string()),
+ server
+ .port
+ .map(|s| format!("port: {}\n", s))
+ .unwrap_or_else(|| "".to_string()),
+ server
+ .username
+ .as_ref()
+ .map(|s| format!("username: {}\n", s))
+ .unwrap_or_else(|| "".to_string()),
+ server
+ .password
+ .as_ref()
+ .map(|s| format!("password: {}\n", s))
+ .unwrap_or_else(|| "".to_string()),
+ );
+ }
+ (Some("name"), None) => {
+ println!("{}", server.name);
+ }
+ (Some("host"), None) => {
+ println!("{}", server.host);
+ }
+ (Some("port"), None) => {
println!(
"{}",
- match var_name {
- "name" => {
- server.name.to_string()
- }
- "host" => {
- server.host.to_string()
- }
- "port" => {
- server
- .port
- .map(|s| s.to_string())
- .unwrap_or(format!("{} not set", "error:".red()))
- }
- "username" => {
- server
- .username
- .as_ref()
- .map(|s| s.to_string())
- .unwrap_or(format!("{} not set", "error:".red()))
- }
- "password" => {
- server
- .password
- .as_ref()
- .map(|s| s.to_string())
- .unwrap_or(format!("{} not set", "error:".red()))
- }
- _ => {
- format!("{} unknown variable", "error:".red())
- }
- }
+ server.port.ok_or(CliError::NotSet("port".to_string()))?
);
}
- } else {
- // var_name is None
- // print server config
- print!(
- "{}{}{}{}",
- format!("host: {}\n", server.host.to_string()),
- server
- .port
- .map(|s| format!("port: {}\n", s))
- .unwrap_or_else(|| "".to_string()),
- server
- .username
- .as_ref()
- .map(|s| format!("username: {}\n", s))
- .unwrap_or_else(|| "".to_string()),
- server
- .password
- .as_ref()
- .map(|s| format!("password: {}\n", s))
- .unwrap_or_else(|| "".to_string()),
- )
+ (Some("username"), None) => {
+ println!(
+ "{}",
+ server
+ .username
+ .as_ref()
+ .ok_or(CliError::NotSet("username".to_string()))?
+ );
+ }
+ (Some("password"), None) => {
+ println!(
+ "{}",
+ server
+ .password
+ .as_ref()
+ .ok_or(CliError::NotSet("password".to_string()))?
+ );
+ }
+ (Some("name"), Some(_)) => {
+ return Err(CliError::UseServerRename)?;
+ }
+ (Some("host"), Some(value)) => {
+ server.host = value;
+ }
+ (Some("port"), Some(value)) => {
+ server.port = Some(value.parse().unwrap());
+ }
+ (Some("username"), Some(value)) => {
+ server.username = Some(value);
+ }
+ (Some("password"), Some(value)) => {
+ server.password = Some(value);
+ //TODO ask stdin if empty
+ }
+ (Some(_), _) => {
+ return Err(CliError::ConfigKeyNotFound(key.unwrap()))?;
+ }
}
- } else {
- // server is None
- error!("server {} not found", server_name);
- }
- } else {
- for server in config.servers.iter() {
- println!("{}", server.name);
}
- }
-}
-
-fn match_server_rename(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) {
- let prev_name = matches.value_of("prev_name").unwrap();
- let next_name = matches.value_of("next_name").unwrap();
- if let Some(server) = config.servers.iter_mut().find(|s| s.name == prev_name) {
- server.name = next_name.to_string();
- } else {
- error!("server {} not found", prev_name);
- }
-}
-
-fn match_server_remove(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) {
- let name = matches.value_of("name").unwrap();
- match config.servers.iter().position(|server| server.name == name) {
- Some(idx) => {
- config.servers.remove(idx);
- }
- None => {
- error!("server {} not found", name);
+ Server::Rename { old_name, new_name } => {
+ config
+ .servers
+ .iter_mut()
+ .find(|s| s.name == old_name)
+ .ok_or(CliError::NoServerFound(old_name))?
+ .name = new_name;
}
- };
-}
-
-fn match_server_add(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) {
- let name = matches.value_of("name").unwrap().to_string();
- let host = matches.value_of("host").unwrap().to_string();
- // optional arguments map None to None
- let port = matches.value_of("port").map(|s| s.parse().unwrap());
- let username = matches.value_of("username").map(|s| s.to_string());
- let password = matches.value_of("password").map(|s| s.to_string());
- if config.servers.iter().any(|s| s.name == name) {
- error!("a server named {} already exists", name);
- } else {
- config.servers.push(ServerConfig {
+ Server::Add {
name,
host,
port,
username,
password,
- });
+ } => {
+ if config.servers.iter().any(|s| s.name == name) {
+ return Err(CliError::ServerAlreadyExists(name))?;
+ } else {
+ config.servers.push(ServerConfig {
+ name,
+ host,
+ port,
+ username,
+ password,
+ });
+ }
+ }
+ Server::Remove { name } => {
+ let idx = config
+ .servers
+ .iter()
+ .position(|s| s.name == name)
+ .ok_or(CliError::NoServerFound(name))?;
+ config.servers.remove(idx);
+ }
+ Server::List => {
+ if config.servers.is_empty() {
+ return Err(CliError::NoServers)?;
+ }
+ let query = config
+ .servers
+ .iter()
+ .map(|e| {
+ let response = send_command(MumCommand::ServerStatus {
+ host: e.host.clone(),
+ port: e.port.unwrap_or(mumlib::DEFAULT_PORT),
+ });
+ response.map(|f| (e, f))
+ })
+ .collect::<Result<Vec<_>, _>>()?;
+ for (server, response) in query
+ .into_iter()
+ .filter(|e| e.1.is_ok())
+ .map(|e| (e.0, e.1.unwrap().unwrap()))
+ {
+ if let CommandResponse::ServerStatus {
+ users, max_users, ..
+ } = response
+ {
+ println!("{} [{}/{}]", server.name, users, max_users)
+ } else {
+ unreachable!()
+ }
+ }
+ }
}
+ Ok(())
}
-fn parse_status(server_state: &mumlib::state::Server) {
+fn parse_state(server_state: &mumlib::state::Server) {
println!(
"Connected to {} as {}",
server_state.host, server_state.username
@@ -579,19 +552,28 @@ fn parse_status(server_state: &mumlib::state::Server) {
}
}
-fn send_command(command: Command) -> Result<mumlib::error::Result<Option<CommandResponse>>, crate::Error> {
- let mut connection = UnixStream::connect(mumlib::SOCKET_PATH).map_err(|_| Error::ConnectionError)?;
+fn send_command(
+ command: MumCommand,
+) -> Result<mumlib::error::Result<Option<CommandResponse>>, CliError> {
+ let mut connection =
+ UnixStream::connect(mumlib::SOCKET_PATH).map_err(|_| CliError::ConnectionError)?;
let serialized = bincode::serialize(&command).unwrap();
- connection.write(&(serialized.len() as u32).to_be_bytes()).map_err(|_| Error::ConnectionError)?;
- connection.write(&serialized).map_err(|_| Error::ConnectionError)?;
+ connection
+ .write(&(serialized.len() as u32).to_be_bytes())
+ .map_err(|_| CliError::ConnectionError)?;
+ connection
+ .write(&serialized)
+ .map_err(|_| CliError::ConnectionError)?;
- connection.read_exact(&mut [0; 4]).map_err(|_| Error::ConnectionError)?;
- bincode::deserialize_from(&mut connection).map_err(|_| Error::ConnectionError)
+ connection
+ .read_exact(&mut [0; 4])
+ .map_err(|_| CliError::ConnectionError)?;
+ bincode::deserialize_from(&mut connection).map_err(|_| CliError::ConnectionError)
}
-fn print_channel(channel: &Channel, depth: usize) {
+fn print_channel(channel: &MumChannel, depth: usize) {
println!(
"{}{}{}",
iter::repeat(INDENTATION).take(depth).collect::<String>(),
@@ -615,14 +597,3 @@ fn print_channel(channel: &Channel, depth: usize) {
print_channel(child, depth + 1);
}
}
-
-#[derive(Debug)]
-enum Error {
- ConnectionError
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- write!(f, "Unable to connect to mumd. Is mumd running?")
- }
-}