1
0
Fork 0
mirror of https://codeberg.org/icewind/haze.git synced 2026-06-03 17:14:08 +02:00

better command parsing

This commit is contained in:
Robin Appelman 2021-03-14 16:25:45 +01:00
commit 30a002ef71
5 changed files with 205 additions and 136 deletions

View file

@ -1,44 +1,115 @@
use crate::cloud::CloudOptions;
use color_eyre::{Report, Result}; use color_eyre::{Report, Result};
use parse_display::Display;
use std::fmt::Display;
use std::str::FromStr; use std::str::FromStr;
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct HazeArgs { pub enum HazeArgs {
pub id: Option<String>, List {
pub command: HazeCommand, filter: Option<String>,
pub options: Vec<String>, },
Start {
options: CloudOptions,
},
Stop {
filter: Option<String>,
},
Test {
options: CloudOptions,
path: Option<String>,
},
Exec {
filter: Option<String>,
command: Vec<String>,
},
Occ {
filter: Option<String>,
command: Vec<String>,
},
Db {
filter: Option<String>,
},
Clean,
Logs {
filter: Option<String>,
},
Open {
filter: Option<String>,
},
} }
impl HazeArgs { impl HazeArgs {
pub fn parse<I, S>(mut args: I) -> Result<HazeArgs> pub fn parse<I, S>(mut args: I) -> Result<HazeArgs>
where where
S: AsRef<str> + ToString, S: AsRef<str> + Into<String> + Display,
I: Iterator<Item = S>, I: Iterator<Item = S>,
{ {
let _bin = args.next().unwrap(); let _bin = args.next();
let (id, command) = match args.next() { let command_or_filter = match args.next() {
Some(sub_or_id) => { Some(s) => s,
if let Ok(command) = sub_or_id.as_ref().parse() { None => return Ok(HazeArgs::List { filter: None }),
(None, command)
} else {
if let Some(sub) = args.next() {
(Some(sub_or_id.to_string()), sub.as_ref().parse()?)
} else {
(Some(sub_or_id.to_string()), HazeCommand::List)
}
}
}
None => (None, HazeCommand::List),
}; };
let options = args.map(|s| s.to_string()).collect(); let (cmd, filter) = match HazeCommand::from_str(command_or_filter.as_ref()) {
Ok(HazeArgs { Ok(cmd) => (cmd, None),
id, Err(_) => {
command, let cmd = match args.next() {
options, Some(cmd) => HazeCommand::from_str(cmd.as_ref())?,
None => {
return Ok(HazeArgs::List {
filter: Some(command_or_filter.into()),
}) })
} }
};
if !cmd.allows_filter() {
return Err(Report::msg(format!(
"{} doesn't allow specifying a filter",
cmd
)));
}
(cmd, Some(command_or_filter.into()))
}
};
match cmd {
HazeCommand::List => Ok(HazeArgs::List {
filter: filter.or_else(|| args.next().map(S::into)),
}),
HazeCommand::Start => {
let mut args = args.peekable();
let options = CloudOptions::parse(&mut args)?;
if let Some(leftover) = args.next() {
return Err(Report::msg(format!("unrecognized option {}", leftover)));
}
Ok(HazeArgs::Start { options })
}
HazeCommand::Stop => Ok(HazeArgs::Stop { filter }),
HazeCommand::Test => {
let mut args = args.peekable();
let options = CloudOptions::parse(&mut args)?;
let path = args.next().map(S::into);
if let Some(leftover) = args.next() {
return Err(Report::msg(format!("unrecognized option {}", leftover)));
}
Ok(HazeArgs::Test { options, path })
}
HazeCommand::Exec => Ok(HazeArgs::Exec {
filter,
command: args.map(S::into).collect(),
}),
HazeCommand::Occ => Ok(HazeArgs::Occ {
filter,
command: args.map(S::into).collect(),
}),
HazeCommand::Db => Ok(HazeArgs::Db { filter }),
HazeCommand::Clean => Ok(HazeArgs::Clean),
HazeCommand::Logs => Ok(HazeArgs::Logs { filter }),
HazeCommand::Open => Ok(HazeArgs::Open { filter }),
}
}
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Display)]
pub enum HazeCommand { pub enum HazeCommand {
List, List,
Start, Start,
@ -72,54 +143,60 @@ impl FromStr for HazeCommand {
} }
} }
impl HazeCommand {
pub fn allows_filter(&self) -> bool {
match self {
HazeCommand::List => true,
HazeCommand::Start => false,
HazeCommand::Stop => true,
HazeCommand::Test => false,
HazeCommand::Exec => true,
HazeCommand::Occ => true,
HazeCommand::Db => true,
HazeCommand::Clean => false,
HazeCommand::Logs => true,
HazeCommand::Open => true,
}
}
}
#[test] #[test]
fn test_arg_parse() { fn test_arg_parse() {
assert_eq!( assert_eq!(
HazeArgs::parse(vec!["haze"].into_iter()).unwrap(), HazeArgs::parse(vec!["haze"].into_iter()).unwrap(),
HazeArgs { HazeArgs::List { filter: None }
id: None,
command: HazeCommand::List,
options: Vec::new(),
}
); );
assert_eq!( assert_eq!(
HazeArgs::parse(vec!["haze", "test"].into_iter()).unwrap(), HazeArgs::parse(vec!["haze", "test"].into_iter()).unwrap(),
HazeArgs { HazeArgs::Test {
id: None, options: Default::default(),
command: HazeCommand::Test, path: None
options: Vec::new(),
} }
); );
assert_eq!( assert_eq!(
HazeArgs::parse(vec!["haze", "asdasd"].into_iter()).unwrap(), HazeArgs::parse(vec!["haze", "asdasd"].into_iter()).unwrap(),
HazeArgs { HazeArgs::List {
id: Some("asdasd".to_string()), filter: Some("asdasd".to_string())
command: HazeCommand::List,
options: Vec::new(),
} }
); );
assert_eq!( assert_eq!(
HazeArgs::parse(vec!["haze", "asdasd", "db"].into_iter()).unwrap(), HazeArgs::parse(vec!["haze", "asdasd", "db"].into_iter()).unwrap(),
HazeArgs { HazeArgs::Db {
id: Some("asdasd".to_string()), filter: Some("asdasd".to_string())
command: HazeCommand::Db,
options: Vec::new(),
} }
); );
assert_eq!( assert_eq!(
HazeArgs::parse(vec!["haze", "exec", "foo", "bar"].into_iter()).unwrap(), HazeArgs::parse(vec!["haze", "exec", "foo", "bar"].into_iter()).unwrap(),
HazeArgs { HazeArgs::Exec {
id: None, filter: None,
command: HazeCommand::Exec, command: vec!["foo".to_string(), "bar".to_string()],
options: vec!["foo".to_string(), "bar".to_string()],
} }
); );
assert_eq!( assert_eq!(
HazeArgs::parse(vec!["haze", "asdasd", "exec", "foo", "bar"].into_iter()).unwrap(), HazeArgs::parse(vec!["haze", "asdasd", "exec", "foo", "bar"].into_iter()).unwrap(),
HazeArgs { HazeArgs::Exec {
id: Some("asdasd".to_string()), filter: Some("asdasd".to_string()),
command: HazeCommand::Exec, command: vec!["foo".to_string(), "bar".to_string()],
options: vec!["foo".to_string(), "bar".to_string()],
} }
); );
} }

View file

@ -11,7 +11,9 @@ use color_eyre::{eyre::WrapErr, Report, Result};
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
use min_id::generate_id; use min_id::generate_id;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display;
use std::fs; use std::fs;
use std::iter::Peekable;
use std::net::IpAddr; use std::net::IpAddr;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::str::FromStr; use std::str::FromStr;
@ -19,85 +21,82 @@ use std::time::Duration;
use tokio::fs::{create_dir_all, remove_dir_all, write}; use tokio::fs::{create_dir_all, remove_dir_all, write};
use tokio::time::sleep; use tokio::time::sleep;
#[derive(Default, Debug, Eq, PartialEq)] #[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct CloudOptions { pub struct CloudOptions {
db: Database, db: Database,
php: PhpVersion, php: PhpVersion,
} }
impl CloudOptions { impl CloudOptions {
pub fn parse<S>(options: Vec<S>) -> Result<(CloudOptions, Vec<S>)> pub fn parse<I, S>(args: &mut Peekable<I>) -> Result<CloudOptions>
where where
S: AsRef<str> + Clone, S: AsRef<str> + Into<String> + Display,
I: Iterator<Item = S>,
{ {
let mut db = Database::default(); let mut db = None;
let mut php = PhpVersion::default(); let mut php = None;
let mut used = 0;
for option in options.iter() { while let Some(option) = args.peek() {
if let Ok(db_option) = Database::from_str(option.as_ref()) { if let Ok(db_option) = Database::from_str(option.as_ref()) {
db = db_option; db = Some(db_option);
used += 1; let _ = args.next();
continue; } else if let Ok(php_option) = PhpVersion::from_str(option.as_ref()) {
php = Some(php_option);
let _ = args.next();
} else {
break;
} }
if let Ok(php_option) = PhpVersion::from_str(option.as_ref()) {
php = php_option; if db.is_some() && php.is_some() {
used += 1; break;
continue;
} }
} }
let rest = options[used..].to_vec(); Ok(CloudOptions {
db: db.unwrap_or_default(),
Ok((CloudOptions { db, php }, rest)) php: php.unwrap_or_default(),
})
} }
} }
#[test] #[test]
fn test_option_parse() { fn test_option_parse() {
let mut args = vec![].into_iter().peekable();
assert_eq!( assert_eq!(
CloudOptions::parse::<&str>(vec![]).unwrap(), CloudOptions::parse::<_, &str>(&mut args).unwrap(),
(CloudOptions::default(), vec![]) CloudOptions::default()
); );
let mut args = vec!["mariadb"].into_iter().peekable();
assert_eq!( assert_eq!(
CloudOptions::parse(vec!["mariadb"]).unwrap(), CloudOptions::parse(&mut args).unwrap(),
(
CloudOptions { CloudOptions {
db: Database::MariaDB, db: Database::MariaDB,
..Default::default() ..Default::default()
}, }
vec![]
)
); );
let mut args = vec!["rest"].into_iter().peekable();
assert_eq!( assert_eq!(
CloudOptions::parse(vec!["rest"]).unwrap(), CloudOptions::parse(&mut args).unwrap(),
(
CloudOptions { CloudOptions {
..Default::default() ..Default::default()
}, }
vec!["rest"]
)
); );
let mut args = vec!["7"].into_iter().peekable();
assert_eq!( assert_eq!(
CloudOptions::parse(vec!["7"]).unwrap(), CloudOptions::parse(&mut args).unwrap(),
(
CloudOptions { CloudOptions {
php: PhpVersion::Php74, php: PhpVersion::Php74,
..Default::default() ..Default::default()
}, }
vec![]
)
); );
let mut args = vec!["7", "pgsql", "rest"].into_iter().peekable();
assert_eq!( assert_eq!(
CloudOptions::parse(vec!["7", "pgsql", "rest"]).unwrap(), CloudOptions::parse(&mut args).unwrap(),
(
CloudOptions { CloudOptions {
php: PhpVersion::Php74, php: PhpVersion::Php74,
db: Database::Postgres, db: Database::Postgres,
..Default::default() ..Default::default()
}, }
vec!["rest"]
)
); );
} }

View file

@ -25,7 +25,7 @@ impl DatabaseFamily {
} }
} }
#[derive(Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
#[allow(dead_code)] #[allow(dead_code)]
pub enum Database { pub enum Database {
Sqlite, Sqlite,

View file

@ -1,8 +1,8 @@
use crate::args::{HazeArgs, HazeCommand}; use crate::args::HazeArgs;
use crate::cloud::{Cloud, CloudOptions}; use crate::cloud::Cloud;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use bollard::Docker; use bollard::Docker;
use color_eyre::{eyre::WrapErr, Report, Result}; use color_eyre::{eyre::WrapErr, Result};
mod args; mod args;
mod cloud; mod cloud;
@ -20,8 +20,8 @@ async fn main() -> Result<()> {
let args = HazeArgs::parse(std::env::args())?; let args = HazeArgs::parse(std::env::args())?;
match args.command { match args {
HazeCommand::Clean => { HazeArgs::Clean => {
let list = Cloud::list(&mut docker, None, &config).await?; let list = Cloud::list(&mut docker, None, &config).await?;
for cloud in list { for cloud in list {
if let Err(e) = cloud.destroy(&mut docker).await { if let Err(e) = cloud.destroy(&mut docker).await {
@ -29,14 +29,9 @@ async fn main() -> Result<()> {
} }
} }
} }
HazeCommand::List => { HazeArgs::List { filter } => {
let list = Cloud::list(&mut docker, args.options.first().cloned(), &config).await?; let list = Cloud::list(&mut docker, filter, &config).await?;
for cloud in list { for cloud in list {
if let Some(filter) = &args.id {
if !cloud.id.contains(filter.as_str()) {
continue;
}
}
match cloud.ip { match cloud.ip {
Some(ip) => println!( Some(ip) => println!(
"Cloud {}, {}, {}, running on http://{}", "Cloud {}, {}, {}, running on http://{}",
@ -54,56 +49,54 @@ async fn main() -> Result<()> {
} }
} }
} }
HazeCommand::Start => { HazeArgs::Start { options } => {
let (options, rest) = CloudOptions::parse(args.options)?;
if let Some(next) = rest.first() {
return Err(Report::msg(format!("Unknown option {}", next)));
}
let cloud = Cloud::create(&mut docker, options, &config).await?; let cloud = Cloud::create(&mut docker, options, &config).await?;
println!("http://{}", cloud.ip.unwrap()); println!("http://{}", cloud.ip.unwrap());
} }
HazeCommand::Stop => { HazeArgs::Stop { filter } => {
let cloud = Cloud::get_by_filter(&mut docker, args.id, &config).await?; let cloud = Cloud::get_by_filter(&mut docker, filter, &config).await?;
cloud.destroy(&mut docker).await?; cloud.destroy(&mut docker).await?;
} }
HazeCommand::Logs => { HazeArgs::Logs { filter } => {
let cloud = Cloud::get_by_filter(&mut docker, args.id, &config).await?; let cloud = Cloud::get_by_filter(&mut docker, filter, &config).await?;
let logs = cloud.logs(&mut docker).await?; let logs = cloud.logs(&mut docker).await?;
for log in logs { for log in logs {
print!("{}", log); print!("{}", log);
} }
} }
HazeCommand::Exec => { HazeArgs::Exec { filter, command } => {
let cloud = Cloud::get_by_filter(&mut docker, args.id, &config).await?; let cloud = Cloud::get_by_filter(&mut docker, filter, &config).await?;
cloud cloud
.exec( .exec(
&mut docker, &mut docker,
if args.options.is_empty() { if command.is_empty() {
vec!["bash".to_string()] vec!["bash".to_string()]
} else { } else {
args.options command
}, },
) )
.await?; .await?;
} }
HazeCommand::Occ => { HazeArgs::Occ {
let cloud = Cloud::get_by_filter(&mut docker, args.id, &config).await?; filter,
let mut options = args.options; mut command,
options.insert(0, "occ".to_string()); } => {
cloud.exec(&mut docker, options).await?; let cloud = Cloud::get_by_filter(&mut docker, filter, &config).await?;
command.insert(0, "occ".to_string());
cloud.exec(&mut docker, command).await?;
} }
HazeCommand::Db => { HazeArgs::Db { filter } => {
let cloud = Cloud::get_by_filter(&mut docker, args.id, &config).await?; let cloud = Cloud::get_by_filter(&mut docker, filter, &config).await?;
cloud.db.exec(&mut docker, &cloud.id).await?; cloud.db.exec(&mut docker, &cloud.id).await?;
} }
HazeCommand::Open => { HazeArgs::Open { filter } => {
let cloud = Cloud::get_by_filter(&mut docker, args.id, &config).await?; let cloud = Cloud::get_by_filter(&mut docker, filter, &config).await?;
match cloud.ip { match cloud.ip {
Some(ip) => opener::open(format!("http://{}", ip))?, Some(ip) => opener::open(format!("http://{}", ip))?,
None => eprintln!("{} is not running", cloud.id), None => eprintln!("{} is not running", cloud.id),
} }
} }
HazeCommand::Test => { HazeArgs::Test { .. } => {
todo!(); todo!();
} }
}; };

View file

@ -7,7 +7,7 @@ use color_eyre::Result;
use maplit::hashmap; use maplit::hashmap;
use std::str::FromStr; use std::str::FromStr;
#[derive(Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
#[allow(dead_code)] #[allow(dead_code)]
pub enum PhpVersion { pub enum PhpVersion {
Latest, Latest,