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

move checkout to new git subcommand

This commit is contained in:
Robin Appelman 2025-11-20 20:48:10 +01:00
commit d86ee53f66
3 changed files with 320 additions and 176 deletions

View file

@ -2,11 +2,28 @@ use crate::cloud::CloudOptions;
use crate::config::{HazeConfig, Preset}; use crate::config::{HazeConfig, Preset};
use crate::service::{Service, ServiceTrait}; use crate::service::{Service, ServiceTrait};
use miette::{IntoDiagnostic, Report, Result}; use miette::{IntoDiagnostic, Report, Result};
use std::fmt::Display; use std::fmt::{Debug, Display};
use std::rc::Rc;
use std::str::FromStr; use std::str::FromStr;
use strum::{Display, EnumIter, EnumMessage, EnumProperty, EnumString, IntoStaticStr}; use strum::{
Display, EnumIter, EnumMessage, EnumProperty, EnumString, IntoEnumIterator, IntoStaticStr,
};
#[derive(Debug, Clone, Eq, PartialEq)] pub trait SubCommand: Display + EnumMessage + EnumProperty + Debug {
fn parent(&self) -> &'static str {
""
}
fn allows_filter(&self) -> bool {
false
}
fn sub_commands(&self) -> Option<Box<dyn Iterator<Item = Box<dyn SubCommand>>>> {
None
}
}
#[derive(Debug, Clone)]
pub enum HazeArgs { pub enum HazeArgs {
List { List {
filter: Option<String>, filter: Option<String>,
@ -65,8 +82,8 @@ pub enum HazeArgs {
filter: Option<String>, filter: Option<String>,
}, },
Proxy, Proxy,
Checkout { Git {
branch: String, operation: GitOperation,
}, },
Env { Env {
filter: Option<String>, filter: Option<String>,
@ -75,7 +92,7 @@ pub enum HazeArgs {
}, },
Update, Update,
Help { Help {
command: Option<HazeCommand>, command: Option<Rc<dyn SubCommand>>,
}, },
Version, Version,
Edit { Edit {
@ -84,6 +101,33 @@ pub enum HazeArgs {
}, },
} }
#[derive(
Debug,
Clone,
Eq,
PartialEq,
Display,
EnumProperty,
EnumString,
IntoStaticStr,
EnumMessage,
EnumIter,
)]
#[strum(serialize_all = "lowercase")]
pub enum GitOperation {
/// Checkout a branch in all apps
///
/// "main" and "master" can be used interchangeably.
#[strum(props(Args = "[branch] branch to checkout"))]
Checkout { branch: String },
}
impl SubCommand for GitOperation {
fn parent(&self) -> &'static str {
" git"
}
}
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum LogService { pub enum LogService {
Service(Service), Service(Service),
@ -271,7 +315,26 @@ impl HazeArgs {
.next() .next()
.map(S::into) .map(S::into)
.ok_or_else(|| Report::msg("No branch provided"))?; .ok_or_else(|| Report::msg("No branch provided"))?;
Ok(HazeArgs::Checkout { branch }) Ok(HazeArgs::Git {
operation: GitOperation::Checkout { branch },
})
}
HazeCommand::Git => {
let operation = args
.next()
.ok_or_else(|| Report::msg("No git operation provided"))?;
match operation.as_ref() {
"checkout" => {
let branch = args
.next()
.map(S::into)
.ok_or_else(|| Report::msg("No branch provided"))?;
Ok(HazeArgs::Git {
operation: GitOperation::Checkout { branch },
})
}
operation => Err(Report::msg(format!("Unknown git operation {operation}"))),
}
} }
HazeCommand::Env => { HazeCommand::Env => {
let mut args = args.map(S::into); let mut args = args.map(S::into);
@ -286,13 +349,21 @@ impl HazeArgs {
} }
HazeCommand::Update => Ok(HazeArgs::Update), HazeCommand::Update => Ok(HazeArgs::Update),
HazeCommand::Help => { HazeCommand::Help => {
let command = args.next(); let command = args
let command = command .next()
.as_ref() .map(|command| match command.as_ref() {
.map(|s| s.as_ref()) "git" => match args.next() {
.map(HazeCommand::from_str) Some(op) => GitOperation::from_str(op.as_ref())
.transpose() .map(|op| Rc::new(op) as Rc<dyn SubCommand>)
.map_err(|_| Report::msg("Unknown command"))?; .map_err(|_| Report::msg("Unknown command")),
None => Ok(Rc::new(HazeCommand::Git) as Rc<dyn SubCommand>),
},
command => HazeCommand::from_str(command)
.map(|command| Rc::new(command) as Rc<dyn SubCommand>)
.map_err(|_| Report::msg("Unknown command")),
})
.transpose()?;
Ok(HazeArgs::Help { command }) Ok(HazeArgs::Help { command })
} }
HazeCommand::Version => Ok(HazeArgs::Version), HazeCommand::Version => Ok(HazeArgs::Version),
@ -372,10 +443,9 @@ pub enum HazeCommand {
Unpin, Unpin,
/// Start the proxy /// Start the proxy
Proxy, Proxy,
/// Checkout a branch in all apps /// Perform git operations on all apps
/// Git,
/// Only switches branches if the target branch exists locally. /// Deprecated, use `haze git checkout` instead
/// "main" and "master" can be used interchangeably.
#[strum(props(Args = "[branch] branch to checkout"))] #[strum(props(Args = "[branch] branch to checkout"))]
Checkout, Checkout,
/// Run command with notify_push environment variables /// Run command with notify_push environment variables
@ -394,8 +464,8 @@ pub enum HazeCommand {
Edit, Edit,
} }
impl HazeCommand { impl SubCommand for HazeCommand {
pub fn allows_filter(&self) -> bool { fn allows_filter(&self) -> bool {
matches!( matches!(
self, self,
HazeCommand::List HazeCommand::List
@ -411,6 +481,13 @@ impl HazeCommand {
| HazeCommand::Edit | HazeCommand::Edit
) )
} }
fn sub_commands(&self) -> Option<Box<dyn Iterator<Item = Box<dyn SubCommand>>>> {
match self {
HazeCommand::Git => Some(Box::new(GitOperation::iter().map(|op| Box::new(op) as _))),
_ => None,
}
}
} }
#[test] #[test]
@ -425,45 +502,49 @@ fn test_arg_parse() {
preset: vec![], preset: vec![],
}; };
assert_eq!( fn assert_eq<A: Debug, B: Debug>(a: A, b: B) {
assert_eq!(format!("{a:?}"), format!("{b:?}"));
}
assert_eq(
HazeArgs::parse(&config, vec!["haze"].into_iter()).unwrap(), HazeArgs::parse(&config, vec!["haze"].into_iter()).unwrap(),
HazeArgs::List { filter: None } HazeArgs::List { filter: None },
); );
assert_eq!( assert_eq(
HazeArgs::parse(&config, vec!["haze", "test"].into_iter()).unwrap(), HazeArgs::parse(&config, vec!["haze", "test"].into_iter()).unwrap(),
HazeArgs::Test { HazeArgs::Test {
options: Default::default(), options: Default::default(),
args: vec![] args: vec![],
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse(&config, vec!["haze", "asdasd"].into_iter()).unwrap(), HazeArgs::parse(&config, vec!["haze", "asdasd"].into_iter()).unwrap(),
HazeArgs::List { HazeArgs::List {
filter: Some("asdasd".to_string()) filter: Some("asdasd".to_string()),
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse(&config, vec!["haze", "asdasd", "db"].into_iter()).unwrap(), HazeArgs::parse(&config, vec!["haze", "asdasd", "db"].into_iter()).unwrap(),
HazeArgs::Db { HazeArgs::Db {
filter: Some("asdasd".to_string()), filter: Some("asdasd".to_string()),
root: false, root: false,
command: Vec::new(), command: Vec::new(),
index: None, index: None,
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse(&config, vec!["haze", "asdasd", "db", "root"].into_iter()).unwrap(), HazeArgs::parse(&config, vec!["haze", "asdasd", "db", "root"].into_iter()).unwrap(),
HazeArgs::Db { HazeArgs::Db {
filter: Some("asdasd".to_string()), filter: Some("asdasd".to_string()),
root: true, root: true,
command: Vec::new(), command: Vec::new(),
index: None, index: None,
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse( HazeArgs::parse(
&config, &config,
vec!["haze", "asdasd", "db", "select", "1"].into_iter() vec!["haze", "asdasd", "db", "select", "1"].into_iter(),
) )
.unwrap(), .unwrap(),
HazeArgs::Db { HazeArgs::Db {
@ -471,12 +552,12 @@ fn test_arg_parse() {
root: false, root: false,
command: vec!["select".to_string(), "1".to_string()], command: vec!["select".to_string(), "1".to_string()],
index: None, index: None,
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse( HazeArgs::parse(
&config, &config,
vec!["haze", "asdasd", "db", "root", "select 1"].into_iter() vec!["haze", "asdasd", "db", "root", "select 1"].into_iter(),
) )
.unwrap(), .unwrap(),
HazeArgs::Db { HazeArgs::Db {
@ -484,12 +565,12 @@ fn test_arg_parse() {
root: true, root: true,
command: vec!["select 1".to_string()], command: vec!["select 1".to_string()],
index: None, index: None,
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse( HazeArgs::parse(
&config, &config,
vec!["haze", "asdasd", "db", "root", "1", "select 1"].into_iter() vec!["haze", "asdasd", "db", "root", "1", "select 1"].into_iter(),
) )
.unwrap(), .unwrap(),
HazeArgs::Db { HazeArgs::Db {
@ -497,12 +578,12 @@ fn test_arg_parse() {
root: true, root: true,
command: vec!["select 1".to_string()], command: vec!["select 1".to_string()],
index: Some("1".into()), index: Some("1".into()),
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse( HazeArgs::parse(
&config, &config,
vec!["haze", "asdasd", "db", "all", "select 1"].into_iter() vec!["haze", "asdasd", "db", "all", "select 1"].into_iter(),
) )
.unwrap(), .unwrap(),
HazeArgs::Db { HazeArgs::Db {
@ -510,21 +591,21 @@ fn test_arg_parse() {
root: false, root: false,
command: vec!["select 1".to_string()], command: vec!["select 1".to_string()],
index: Some("all".into()), index: Some("all".into()),
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse(&config, vec!["haze", "exec", "foo", "bar"].into_iter()).unwrap(), HazeArgs::parse(&config, vec!["haze", "exec", "foo", "bar"].into_iter()).unwrap(),
HazeArgs::Exec { HazeArgs::Exec {
filter: None, filter: None,
service: None, service: None,
command: vec!["foo".to_string(), "bar".to_string()], command: vec!["foo".to_string(), "bar".to_string()],
root: false, root: false,
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse( HazeArgs::parse(
&config, &config,
vec!["haze", "exec", "root", "foo", "bar"].into_iter() vec!["haze", "exec", "root", "foo", "bar"].into_iter(),
) )
.unwrap(), .unwrap(),
HazeArgs::Exec { HazeArgs::Exec {
@ -532,12 +613,12 @@ fn test_arg_parse() {
service: None, service: None,
command: vec!["foo".to_string(), "bar".to_string()], command: vec!["foo".to_string(), "bar".to_string()],
root: true, root: true,
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse( HazeArgs::parse(
&config, &config,
vec!["haze", "asdasd", "exec", "foo", "bar"].into_iter() vec!["haze", "asdasd", "exec", "foo", "bar"].into_iter(),
) )
.unwrap(), .unwrap(),
HazeArgs::Exec { HazeArgs::Exec {
@ -545,12 +626,12 @@ fn test_arg_parse() {
service: None, service: None,
command: vec!["foo".to_string(), "bar".to_string()], command: vec!["foo".to_string(), "bar".to_string()],
root: false, root: false,
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse( HazeArgs::parse(
&config, &config,
vec!["haze", "asdasd", "exec", "db", "foo", "bar"].into_iter() vec!["haze", "asdasd", "exec", "db", "foo", "bar"].into_iter(),
) )
.unwrap(), .unwrap(),
HazeArgs::Exec { HazeArgs::Exec {
@ -558,28 +639,28 @@ fn test_arg_parse() {
service: Some(ExecService::Db), service: Some(ExecService::Db),
command: vec!["foo".to_string(), "bar".to_string()], command: vec!["foo".to_string(), "bar".to_string()],
root: false, root: false,
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse(&config, vec!["haze", "test", "foo", "bar"].into_iter()).unwrap(), HazeArgs::parse(&config, vec!["haze", "test", "foo", "bar"].into_iter()).unwrap(),
HazeArgs::Test { HazeArgs::Test {
options: Default::default(), options: Default::default(),
args: vec!["foo".into(), "bar".into()] args: vec!["foo".into(), "bar".into()],
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse(&config, vec!["haze", "logs", "-f", "smb"].into_iter()).unwrap(), HazeArgs::parse(&config, vec!["haze", "logs", "-f", "smb"].into_iter()).unwrap(),
HazeArgs::Logs { HazeArgs::Logs {
filter: None, filter: None,
follow: true, follow: true,
service: Some(LogService::from_type(&[], "smb").unwrap()), service: Some(LogService::from_type(&[], "smb").unwrap()),
count: None, count: None,
} },
); );
assert_eq!( assert_eq(
HazeArgs::parse( HazeArgs::parse(
&config, &config,
vec!["haze", "asdasd", "logs", "smb", "123"].into_iter() vec!["haze", "asdasd", "logs", "smb", "123"].into_iter(),
) )
.unwrap(), .unwrap(),
HazeArgs::Logs { HazeArgs::Logs {
@ -587,6 +668,6 @@ fn test_arg_parse() {
follow: false, follow: false,
service: Some(LogService::from_type(&[], "smb").unwrap()), service: Some(LogService::from_type(&[], "smb").unwrap()),
count: Some(123), count: Some(123),
} },
); );
} }

View file

@ -1,4 +1,4 @@
use crate::args::HazeCommand; use crate::args::{HazeCommand, SubCommand};
use crate::database::DatabaseFamily; use crate::database::DatabaseFamily;
use crate::php::PhpVersion; use crate::php::PhpVersion;
use crate::service::ServiceType; use crate::service::ServiceType;
@ -6,114 +6,9 @@ use owo_colors::colors::xterm::Gray;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use strum::{EnumMessage, EnumProperty, IntoEnumIterator}; use strum::{EnumMessage, EnumProperty, IntoEnumIterator};
pub fn help(command: Option<HazeCommand>) { pub fn help(command: Option<&dyn SubCommand>) {
if let Some(command) = command { if let Some(command) = command {
println!("{}", command.get_documentation().unwrap_or_default()); subcommand_help(command);
println!();
print!(
"{} {}{} {}",
"Usage:".bright_yellow().bold(),
"haze".blue(),
if command.allows_filter() {
" [filter]".green()
} else {
"".green()
},
command.blue(),
);
let instance_args = command.get_bool("InstanceArgs").unwrap_or_default();
if instance_args {
print!(" {}", "[php version]".green());
print!(" {}", "[database type]".green());
print!(" {}", "[services]".green());
}
let args = if let Some(args) = command.get_str("Args") {
let args: &str = args;
print!(" {}", "[arguments]".green());
args.strip_prefix("[")
.unwrap_or(args)
.split(" [")
.filter_map(|arg| arg.split_once("] "))
.collect::<Vec<_>>()
} else {
vec![]
};
println!();
println!();
if instance_args {
println!("{}", "Php versions:".yellow().bold());
for php in PhpVersion::supported_versions() {
println!(" {}", php.blue());
}
println!();
println!("{}", "Database types:".yellow().bold());
let max_db_len = DatabaseFamily::iter()
.map(|service| <&'static str>::from(service).len())
.max()
.unwrap_or_default();
for db in DatabaseFamily::iter() {
let db: DatabaseFamily = db;
let db_str: &'static str = db.into();
let versions = match db.get_str("Versions") {
Some(versions) => {
let versions: Vec<_> = versions
.split(' ')
.map(|version| format!("{}{}{}", db.blue(), ":".blue(), version.blue()))
.collect();
Some(versions.join(", "))
}
None => None,
};
print!(" {}{} ", db.blue(), " ".repeat(max_db_len - db_str.len()));
if let Some(versions) = versions {
println!("supported versions: {versions}");
} else {
println!();
}
}
println!();
println!("{}", "Services:".yellow().bold());
let max_service_len = ServiceType::iter()
.map(|service| <&'static str>::from(service).len())
.max()
.unwrap_or_default();
for service in ServiceType::iter() {
let service: ServiceType = service;
let service_str: &'static str = service.into();
println!(
" {}{} {}",
service.blue(),
" ".repeat(max_service_len - service_str.len()),
service.get_documentation().unwrap_or_default(),
);
}
}
println!();
if !args.is_empty() {
let max_arg_len = args
.iter()
.map(|(arg, _)| arg.len())
.max()
.unwrap_or_default();
println!("{}", "Arguments:".yellow().bold());
for (arg, desc) in args {
println!(
" {}{}{}{} {}",
"[".green(),
arg.green(),
"]".green(),
" ".repeat(max_arg_len - arg.len()),
desc
);
}
}
} else { } else {
println!( println!(
"{} {} {}", "{} {} {}",
@ -171,3 +66,169 @@ pub fn help(command: Option<HazeCommand>) {
); );
} }
} }
fn subcommand_help(command: &dyn SubCommand) {
println!("{}", command.get_documentation().unwrap_or_default());
println!();
print!(
"{} {}{}{} {}",
"Usage:".bright_yellow().bold(),
"haze".blue(),
if command.allows_filter() {
" [filter]".green()
} else {
"".green()
},
command.parent().blue(),
command.blue(),
);
let instance_args = command.get_bool("InstanceArgs").unwrap_or_default();
if instance_args {
print!(" {}", "[php version]".green());
print!(" {}", "[database type]".green());
print!(" {}", "[services]".green());
}
let args = if let Some(args) = command.get_str("Args") {
let args: &str = args;
print!(" {}", "[arguments]".green());
args.strip_prefix("[")
.unwrap_or(args)
.split(" [")
.filter_map(|arg| arg.split_once("] "))
.collect::<Vec<_>>()
} else {
vec![]
};
println!();
println!();
if instance_args {
println!("{}", "Php versions:".yellow().bold());
for php in PhpVersion::supported_versions() {
println!(" {}", php.blue());
}
println!();
println!("{}", "Database types:".yellow().bold());
let max_db_len = DatabaseFamily::iter()
.map(|service| service.to_string().len())
.max()
.unwrap_or_default();
for db in DatabaseFamily::iter() {
let db: DatabaseFamily = db;
let db_str: &'static str = db.into();
let versions = match db.get_str("Versions") {
Some(versions) => {
let versions: Vec<_> = versions
.split(' ')
.map(|version| format!("{}{}{}", db.blue(), ":".blue(), version.blue()))
.collect();
Some(versions.join(", "))
}
None => None,
};
print!(" {}{} ", db.blue(), " ".repeat(max_db_len - db_str.len()));
if let Some(versions) = versions {
println!("supported versions: {versions}");
} else {
println!();
}
}
println!();
println!("{}", "Services:".yellow().bold());
let max_service_len = ServiceType::iter()
.map(|service| service.to_string().len())
.max()
.unwrap_or_default();
for service in ServiceType::iter() {
let service: ServiceType = service;
let service_str: &'static str = service.into();
println!(
" {}{} {}",
service.blue(),
" ".repeat(max_service_len - service_str.len()),
service.get_documentation().unwrap_or_default(),
);
}
}
if let Some(sub_commands) = command.sub_commands() {
println!();
println!("{}", "Commands:".yellow().bold());
let max_command_len = command
.sub_commands()
.unwrap()
.map(|sub_command| sub_command.to_string().len())
.max()
.unwrap();
let max_doc_len = command
.sub_commands()
.unwrap()
.map(|sub_command| {
sub_command
.get_documentation()
.unwrap_or_default()
.split('\n')
.next()
.unwrap()
.len()
})
.max()
.unwrap();
for sub_command in sub_commands {
let command_str = sub_command.to_string();
let mut len = command_str.len();
if command_str.starts_with("--") {
len -= 2;
}
let doc: &str = sub_command.get_documentation().unwrap_or_default();
let doc = doc.split('\n').next().unwrap();
println!(
" {}{} {}{} {}",
sub_command.blue(),
" ".repeat(max_command_len - len),
doc,
" ".repeat(max_doc_len - doc.len()),
if sub_command.allows_filter() {
"- supports filter".fg::<Gray>()
} else {
"".fg::<Gray>()
},
);
}
println!();
println!(
"See {} {} {} for more information about a {}",
"haze help".blue(),
command.blue(),
"<COMMAND>".green(),
"<COMMAND>".green()
);
}
println!();
if !args.is_empty() {
let max_arg_len = args
.iter()
.map(|(arg, _)| arg.len())
.max()
.unwrap_or_default();
println!("{}", "Arguments:".yellow().bold());
for (arg, desc) in args {
println!(
" {}{}{}{} {}",
"[".green(),
arg.green(),
"]".green(),
" ".repeat(max_arg_len - arg.len()),
desc
);
}
}
}

View file

@ -1,6 +1,6 @@
extern crate core; extern crate core;
use crate::args::{ExecService, HazeArgs}; use crate::args::{ExecService, GitOperation, HazeArgs};
use crate::cloud::{Cloud, CloudOptions}; use crate::cloud::{Cloud, CloudOptions};
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::database::DatabaseFamily; use crate::database::DatabaseFamily;
@ -362,9 +362,11 @@ async fn main() -> Result<ExitCode> {
HazeArgs::Proxy => { HazeArgs::Proxy => {
proxy(docker, config).await?; proxy(docker, config).await?;
} }
HazeArgs::Checkout { branch } => { HazeArgs::Git { operation } => match operation {
GitOperation::Checkout { branch } => {
checkout_all(&config.sources_root, &branch)?; checkout_all(&config.sources_root, &branch)?;
} }
},
HazeArgs::Env { HazeArgs::Env {
filter, filter,
command, command,
@ -452,7 +454,7 @@ async fn main() -> Result<ExitCode> {
println!("haze v{}", VERSION); println!("haze v{}", VERSION);
} }
HazeArgs::Help { command } => { HazeArgs::Help { command } => {
help(command); help(command.as_deref());
} }
HazeArgs::Edit { filter, path } => { HazeArgs::Edit { filter, path } => {
let cloud = Cloud::get_by_filter(&docker, filter, &config).await?; let cloud = Cloud::get_by_filter(&docker, filter, &config).await?;