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:
parent
6041587c05
commit
30a002ef71
5 changed files with 205 additions and 136 deletions
177
src/args.rs
177
src/args.rs
|
|
@ -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()],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
77
src/cloud.rs
77
src/cloud.rs
|
|
@ -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"]
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ impl DatabaseFamily {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Database {
|
||||
Sqlite,
|
||||
|
|
|
|||
63
src/main.rs
63
src/main.rs
|
|
@ -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!();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue