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 parse_display::Display;
use std::fmt::Display;
use std::str::FromStr;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct HazeArgs {
pub id: Option<String>,
pub command: HazeCommand,
pub options: Vec<String>,
pub enum HazeArgs {
List {
filter: Option<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 {
pub fn parse<I, S>(mut args: I) -> Result<HazeArgs>
where
S: AsRef<str> + ToString,
S: AsRef<str> + Into<String> + Display,
I: Iterator<Item = S>,
{
let _bin = args.next().unwrap();
let (id, command) = match args.next() {
Some(sub_or_id) => {
if let Ok(command) = sub_or_id.as_ref().parse() {
(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 _bin = args.next();
let command_or_filter = match args.next() {
Some(s) => s,
None => return Ok(HazeArgs::List { filter: None }),
};
let options = args.map(|s| s.to_string()).collect();
Ok(HazeArgs {
id,
command,
options,
let (cmd, filter) = match HazeCommand::from_str(command_or_filter.as_ref()) {
Ok(cmd) => (cmd, None),
Err(_) => {
let cmd = match args.next() {
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 {
List,
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]
fn test_arg_parse() {
assert_eq!(
HazeArgs::parse(vec!["haze"].into_iter()).unwrap(),
HazeArgs {
id: None,
command: HazeCommand::List,
options: Vec::new(),
}
HazeArgs::List { filter: None }
);
assert_eq!(
HazeArgs::parse(vec!["haze", "test"].into_iter()).unwrap(),
HazeArgs {
id: None,
command: HazeCommand::Test,
options: Vec::new(),
HazeArgs::Test {
options: Default::default(),
path: None
}
);
assert_eq!(
HazeArgs::parse(vec!["haze", "asdasd"].into_iter()).unwrap(),
HazeArgs {
id: Some("asdasd".to_string()),
command: HazeCommand::List,
options: Vec::new(),
HazeArgs::List {
filter: Some("asdasd".to_string())
}
);
assert_eq!(
HazeArgs::parse(vec!["haze", "asdasd", "db"].into_iter()).unwrap(),
HazeArgs {
id: Some("asdasd".to_string()),
command: HazeCommand::Db,
options: Vec::new(),
HazeArgs::Db {
filter: Some("asdasd".to_string())
}
);
assert_eq!(
HazeArgs::parse(vec!["haze", "exec", "foo", "bar"].into_iter()).unwrap(),
HazeArgs {
id: None,
command: HazeCommand::Exec,
options: vec!["foo".to_string(), "bar".to_string()],
HazeArgs::Exec {
filter: None,
command: vec!["foo".to_string(), "bar".to_string()],
}
);
assert_eq!(
HazeArgs::parse(vec!["haze", "asdasd", "exec", "foo", "bar"].into_iter()).unwrap(),
HazeArgs {
id: Some("asdasd".to_string()),
command: HazeCommand::Exec,
options: vec!["foo".to_string(), "bar".to_string()],
HazeArgs::Exec {
filter: Some("asdasd".to_string()),
command: 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 min_id::generate_id;
use std::collections::HashMap;
use std::fmt::Display;
use std::fs;
use std::iter::Peekable;
use std::net::IpAddr;
use std::os::unix::fs::MetadataExt;
use std::str::FromStr;
@ -19,85 +21,82 @@ use std::time::Duration;
use tokio::fs::{create_dir_all, remove_dir_all, write};
use tokio::time::sleep;
#[derive(Default, Debug, Eq, PartialEq)]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct CloudOptions {
db: Database,
php: PhpVersion,
}
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
S: AsRef<str> + Clone,
S: AsRef<str> + Into<String> + Display,
I: Iterator<Item = S>,
{
let mut db = Database::default();
let mut php = PhpVersion::default();
let mut used = 0;
let mut db = None;
let mut php = None;
for option in options.iter() {
while let Some(option) = args.peek() {
if let Ok(db_option) = Database::from_str(option.as_ref()) {
db = db_option;
used += 1;
continue;
db = Some(db_option);
let _ = args.next();
} 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;
used += 1;
continue;
if db.is_some() && php.is_some() {
break;
}
}
let rest = options[used..].to_vec();
Ok((CloudOptions { db, php }, rest))
Ok(CloudOptions {
db: db.unwrap_or_default(),
php: php.unwrap_or_default(),
})
}
}
#[test]
fn test_option_parse() {
let mut args = vec![].into_iter().peekable();
assert_eq!(
CloudOptions::parse::<&str>(vec![]).unwrap(),
(CloudOptions::default(), vec![])
CloudOptions::parse::<_, &str>(&mut args).unwrap(),
CloudOptions::default()
);
let mut args = vec!["mariadb"].into_iter().peekable();
assert_eq!(
CloudOptions::parse(vec!["mariadb"]).unwrap(),
(
CloudOptions::parse(&mut args).unwrap(),
CloudOptions {
db: Database::MariaDB,
..Default::default()
},
vec![]
)
}
);
let mut args = vec!["rest"].into_iter().peekable();
assert_eq!(
CloudOptions::parse(vec!["rest"]).unwrap(),
(
CloudOptions::parse(&mut args).unwrap(),
CloudOptions {
..Default::default()
},
vec!["rest"]
)
}
);
let mut args = vec!["7"].into_iter().peekable();
assert_eq!(
CloudOptions::parse(vec!["7"]).unwrap(),
(
CloudOptions::parse(&mut args).unwrap(),
CloudOptions {
php: PhpVersion::Php74,
..Default::default()
},
vec![]
)
}
);
let mut args = vec!["7", "pgsql", "rest"].into_iter().peekable();
assert_eq!(
CloudOptions::parse(vec!["7", "pgsql", "rest"]).unwrap(),
(
CloudOptions::parse(&mut args).unwrap(),
CloudOptions {
php: PhpVersion::Php74,
db: Database::Postgres,
..Default::default()
},
vec!["rest"]
)
}
);
}

View file

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

View file

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

View file

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