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

command help

This commit is contained in:
Robin Appelman 2025-09-01 18:17:06 +02:00
commit f928547ac2
6 changed files with 350 additions and 106 deletions

View file

@ -2,10 +2,9 @@ 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 parse_display::Display;
use std::fmt::Display; use std::fmt::Display;
use std::str::FromStr; use std::str::FromStr;
use strum::{EnumIter, EnumMessage, EnumString, IntoStaticStr}; use strum::{Display, EnumIter, EnumMessage, EnumProperty, EnumString, IntoStaticStr};
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum HazeArgs { pub enum HazeArgs {
@ -74,7 +73,9 @@ pub enum HazeArgs {
args: Vec<String>, args: Vec<String>,
}, },
Update, Update,
Help, Help {
command: Option<HazeCommand>,
},
Version, Version,
} }
@ -270,42 +271,79 @@ impl HazeArgs {
}) })
} }
HazeCommand::Update => Ok(HazeArgs::Update), HazeCommand::Update => Ok(HazeArgs::Update),
HazeCommand::Help => Ok(HazeArgs::Help), HazeCommand::Help => {
let command = args.next();
let command = command
.as_ref()
.map(|s| s.as_ref())
.map(HazeCommand::from_str)
.transpose()
.map_err(|_| Report::msg("Unknown command"))?;
Ok(HazeArgs::Help { command })
}
HazeCommand::Version => Ok(HazeArgs::Version), HazeCommand::Version => Ok(HazeArgs::Version),
} }
} }
} }
#[derive( #[derive(
Debug, Clone, Copy, Eq, PartialEq, Display, IntoStaticStr, EnumIter, EnumString, EnumMessage, Debug,
Clone,
Copy,
Eq,
PartialEq,
Display,
IntoStaticStr,
EnumIter,
EnumString,
EnumMessage,
EnumProperty,
)] )]
#[strum(serialize_all = "lowercase")] #[strum(serialize_all = "lowercase")]
pub enum HazeCommand { pub enum HazeCommand {
/// List all instances /// List all instances
List, List,
/// Start a new instance /// Start a new instance
#[strum(props(InstanceArgs = true))]
Start, Start,
/// Stop an instance /// Stop an instance
Stop, Stop,
/// Run tests in a new instance /// Run tests in a new instance
#[strum(props(
InstanceArgs = true,
Args = "[phpunit arguments] arguments to pass to phpunit"
))]
Test, Test,
/// Run a command in an instance /// Run a command in an instance
#[strum(props(
Args = "[service] run command on a service container instead [command] command to run"
))]
Exec, Exec,
/// Run an occ command in an instance /// Run an occ command in an instance
#[strum(props(Args = "[occ arguments] arguments to pass to occ"))]
Occ, Occ,
/// Connect to the database of an instance /// Connect to the database of an instance
#[strum(props(
Args = "[root] connect to the database as root [db index] database instance to use for sharded setup [sql] sql command to run"
))]
Db, Db,
/// Remove all non-pinned instances /// Remove all non-pinned instances
Clean, Clean,
/// View the logs from an instance or service /// View the logs from an instance or service
#[strum(props(
Args = "[service] service to show logs from [follow] show logs lines as they appear [count] number of lines to show"
))]
Logs, Logs,
/// Open an instance in the browser /// Open an instance in the browser
Open, Open,
/// Run code formatting from a new instance /// Run code formatting from a new instance
#[strum(props(Args = "[path] path to format"))]
Fmt, Fmt,
/// Run integration tests in a new instance /// Run integration tests in a new instance
#[strum(props(InstanceArgs = true, Args = "[args] arguments to pass to behat"))]
Integration, Integration,
/// Start a shell in an empirical instance /// Start a shell in an empirical instance
#[strum(props(InstanceArgs = true, Args = "[command] command to run"))]
Shell, Shell,
/// Pin an instance /// Pin an instance
Pin, Pin,
@ -314,34 +352,39 @@ pub enum HazeCommand {
/// Start the proxy /// Start the proxy
Proxy, Proxy,
/// Checkout a branch in all apps /// Checkout a branch in all apps
///
/// Only switches branches if the target branch exists locally.
/// "main" and "master" can be used interchangeably.
#[strum(props(Args = "[branch] branch to checkout"))]
Checkout, Checkout,
/// Run command with notify_push environment variables /// Run command with notify_push environment variables
Env, Env,
#[strum(props(Args = "[command] command to run with environment variables"))]
/// Update docker images /// Update docker images
Update, Update,
/// Show help text /// Show help text
#[strum(serialize = "help", serialize = "--help")] #[strum(serialize = "--help", to_string = "help")]
Help, Help,
/// Show version number /// Show version number
#[strum(serialize = "version", serialize = "--version")] #[strum(serialize = "--version", to_string = "version")]
Version, Version,
} }
impl HazeCommand { impl HazeCommand {
pub fn allows_filter(&self) -> bool { pub fn allows_filter(&self) -> bool {
match self { matches!(
self,
HazeCommand::List HazeCommand::List
| HazeCommand::Stop | HazeCommand::Stop
| HazeCommand::Exec | HazeCommand::Exec
| HazeCommand::Occ | HazeCommand::Occ
| HazeCommand::Db | HazeCommand::Db
| HazeCommand::Logs | HazeCommand::Logs
| HazeCommand::Open | HazeCommand::Open
| HazeCommand::Pin | HazeCommand::Pin
| HazeCommand::Unpin | HazeCommand::Unpin
| HazeCommand::Env => true, | HazeCommand::Env
_ => false, )
}
} }
} }

View file

@ -9,26 +9,26 @@ use std::io::{stdout, Stdout};
use std::net::IpAddr; use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use strum::{Display, EnumIter, EnumProperty, IntoStaticStr};
use tokio::time::{sleep, timeout}; use tokio::time::{sleep, timeout};
#[derive(Eq, PartialEq)] #[derive(Eq, PartialEq, IntoStaticStr, Copy, Clone, EnumIter, EnumProperty, Display)]
#[strum(serialize_all = "lowercase")]
pub enum DatabaseFamily { pub enum DatabaseFamily {
Sqlite, Sqlite,
#[strum(props(Versions = "5 5.6 5.7 8"))]
Mysql, Mysql,
#[strum(props(Versions = "10 10.1 10.2 10.3 10.4 10.5"))]
MariaDB, MariaDB,
#[strum(props(Versions = "9 10 11 12 13 14"))]
Postgres, Postgres,
#[strum(props(Versions = "21"))]
Oracle, Oracle,
} }
impl DatabaseFamily { impl DatabaseFamily {
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
match self { self.into()
DatabaseFamily::Sqlite => "sqlite",
DatabaseFamily::Mysql => "mysql",
DatabaseFamily::MariaDB => "mariadb",
DatabaseFamily::Postgres => "pgsql",
DatabaseFamily::Oracle => "oci",
}
} }
} }
@ -96,6 +96,7 @@ impl FromStr for Database {
"postgresql:13" => Ok(Database::Postgres13), "postgresql:13" => Ok(Database::Postgres13),
"postgresql:14" => Ok(Database::Postgres14), "postgresql:14" => Ok(Database::Postgres14),
"oracle" => Ok(Database::Oracle), "oracle" => Ok(Database::Oracle),
"oracle:21" => Ok(Database::Oracle),
"oci" => Ok(Database::Oracle), "oci" => Ok(Database::Oracle),
_ => Err(Report::msg("Unknown db type")), _ => Err(Report::msg("Unknown db type")),
} }

View file

@ -1,44 +1,173 @@
use crate::args::HazeCommand; use crate::args::HazeCommand;
use crate::database::DatabaseFamily;
use crate::php::PhpVersion;
use crate::service::ServiceType;
use owo_colors::colors::xterm::Gray; use owo_colors::colors::xterm::Gray;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use strum::{EnumMessage, IntoEnumIterator}; use strum::{EnumMessage, EnumProperty, IntoEnumIterator};
pub fn help() { pub fn help(command: Option<HazeCommand>) {
println!( if let Some(command) = command {
"{} {} {}", println!("{}", command.get_documentation().unwrap_or_default());
"Usage:".bright_yellow().bold(), println!();
"haze".blue(), print!(
"[filter] <COMMAND> [arguments]".green() "{} {}{} {}",
); "Usage:".bright_yellow().bold(),
println!(); "haze".blue(),
println!("{}", "Commands:".yellow().bold());
let max_command_len = HazeCommand::iter()
.map(|command| <&'static str>::from(command).len())
.max()
.unwrap();
let max_doc_len = HazeCommand::iter()
.map(|command| command.get_documentation().unwrap_or_default().len())
.max()
.unwrap();
for command in HazeCommand::iter() {
let command: HazeCommand = command;
let command_str = <&'static str>::from(command);
let mut len = command_str.len();
if command_str.starts_with("--") {
len -= 2;
}
println!(
" {}{} {}{} {}",
command.blue(),
" ".repeat(max_command_len - len),
command.get_documentation().unwrap_or_default(),
" ".repeat(max_doc_len - command.get_documentation().unwrap_or_default().len()),
if command.allows_filter() { if command.allows_filter() {
"- supports filter".fg::<Gray>() " [filter]".green()
} else { } else {
"".fg::<Gray>() "".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 {
println!(
"{} {} {}",
"Usage:".bright_yellow().bold(),
"haze".blue(),
"[filter] <COMMAND> [arguments]".green()
);
println!();
println!("{}", "Commands:".yellow().bold());
let max_command_len = HazeCommand::iter()
.map(|command| <&'static str>::from(command).len())
.max()
.unwrap();
let max_doc_len = HazeCommand::iter()
.map(|command| {
command
.get_documentation()
.unwrap_or_default()
.split('\n')
.next()
.unwrap()
.len()
})
.max()
.unwrap();
for command in HazeCommand::iter() {
let command: HazeCommand = command;
let command_str = <&'static str>::from(command);
let mut len = command_str.len();
if command_str.starts_with("--") {
len -= 2;
}
let doc: &str = command.get_documentation().unwrap_or_default();
let doc = doc.split('\n').next().unwrap();
println!(
" {}{} {}{} {}",
command.blue(),
" ".repeat(max_command_len - len),
doc,
" ".repeat(max_doc_len - doc.len()),
if command.allows_filter() {
"- supports filter".fg::<Gray>()
} else {
"".fg::<Gray>()
},
);
}
println!();
println!(
"See {} {} for more information about a {}",
"haze help".blue(),
"<COMMAND>".green(),
"<COMMAND>".green()
); );
} }
} }

View file

@ -449,8 +449,8 @@ async fn main() -> Result<ExitCode> {
const VERSION: &str = env!("CARGO_PKG_VERSION"); const VERSION: &str = env!("CARGO_PKG_VERSION");
println!("haze v{}", VERSION); println!("haze v{}", VERSION);
} }
HazeArgs::Help => { HazeArgs::Help { command } => {
help(); help(command);
} }
}; };

View file

@ -67,6 +67,10 @@ impl FromStr for PhpVersion {
} }
impl PhpVersion { impl PhpVersion {
pub fn supported_versions() -> &'static [&'static str] {
&["8.1", "8.2", "8.3", "8.4"]
}
pub fn image(&self) -> &'static str { pub fn image(&self) -> &'static str {
// for now only 7.4 // for now only 7.4
match self { match self {

View file

@ -42,6 +42,7 @@ use std::iter::empty;
use std::net::IpAddr; use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use strum::{Display, EnumIter, EnumMessage, EnumString, IntoStaticStr};
use tokio::time::{sleep, timeout}; use tokio::time::{sleep, timeout};
#[async_trait::async_trait] #[async_trait::async_trait]
@ -192,6 +193,78 @@ impl ServiceTrait for RedisTls {
} }
} }
#[derive(
Copy, Clone, Debug, PartialEq, EnumString, EnumMessage, EnumIter, IntoStaticStr, Display,
)]
#[strum(serialize_all = "kebab-case")]
pub enum ServiceType {
/// S3 Primary storage and external storage
S3,
/// S3 multi-object store Primary storage and external storage
S3m,
/// S3 multi-bucket Primary storage and external storage
S3mb,
/// Azure Primary storage and external storage
Azure,
/// Ldap user backend
Ldap,
/// OnlyOffice
OnlyOffice,
/// Libre office online
Office,
/// notify_push
Push,
/// Smb external storage
Smb,
/// Database sharding
#[strum(serialize = "sharding", serialize = "sharded")]
Sharding,
/// Database sharding migration
#[strum(serialize = "sharding-migrate", serialize = "sharded-migrate")]
ShardingMigrate,
/// Database sharding migration, with the shards unset
#[strum(
serialize = "sharding-migrate-unset",
serialize = "sharded-migrate-unset"
)]
ShardingMigrateUnset,
/// Database sharding with a single shard
SingleShard,
/// WebDav external storage
Dav,
/// Sftp external storage
Sftp,
/// ownCloud instance for migration
Oc,
/// Imaginary for preview generation
Imaginary,
/// Kaspersky antivirus in http mode
Kaspersky,
/// Kaspersky antivirus in icap mode
KasperskyIcap,
/// Kaspersky antivirus in local binary
#[strum(serialize = "clamav", serialize = "clam")]
ClamAv,
/// Kaspersky antivirus in external socket mode
#[strum(serialize = "clamav-external", serialize = "clam-external")]
ClamAvExternal,
/// Kaspersky antivirus in local socket mode
#[strum(serialize = "clamav-socket", serialize = "clam-socket")]
ClamAvSocket,
/// Kaspersky antivirus in icap mode
#[strum(serialize = "clamav-icap", serialize = "clam-icap")]
ClamAvIcap,
/// Kaspersky antivirus in icap mode with TLS
#[strum(serialize = "clamav-icap-tls", serialize = "clam-icap-tls")]
ClamAvIcapTls,
/// Mail server
Mail,
/// External redis instance
Redis,
/// External redis instance with TLS
RedisTls,
}
#[enum_dispatch] #[enum_dispatch]
#[derive(Clone, Eq, PartialEq, Debug)] #[derive(Clone, Eq, PartialEq, Debug)]
pub enum Service { pub enum Service {
@ -224,52 +297,46 @@ pub enum Service {
impl Service { impl Service {
pub fn from_type(presets: &[Preset], ty: &str) -> Option<Vec<Self>> { pub fn from_type(presets: &[Preset], ty: &str) -> Option<Vec<Self>> {
match ty { if let Ok(ty) = ServiceType::from_str(ty) {
"s3" => Some(vec![Service::ObjectStore(ObjectStore::S3)]), match ty {
"s3m" => Some(vec![Service::ObjectStore(ObjectStore::S3m)]), ServiceType::S3 => Some(vec![Service::ObjectStore(ObjectStore::S3)]),
"s3mb" => Some(vec![Service::ObjectStore(ObjectStore::S3mb)]), ServiceType::S3m => Some(vec![Service::ObjectStore(ObjectStore::S3m)]),
"azure" => Some(vec![Service::ObjectStore(ObjectStore::Azure)]), ServiceType::S3mb => Some(vec![Service::ObjectStore(ObjectStore::S3mb)]),
"ldap" => Some(vec![Service::Ldap(Ldap), Service::LdapAdmin(LdapAdmin)]), ServiceType::Azure => Some(vec![Service::ObjectStore(ObjectStore::Azure)]),
"onlyoffice" => Some(vec![Service::OnlyOffice(OnlyOffice)]), ServiceType::Ldap => Some(vec![Service::Ldap(Ldap), Service::LdapAdmin(LdapAdmin)]),
"office" => Some(vec![Service::Office(Office)]), ServiceType::OnlyOffice => Some(vec![Service::OnlyOffice(OnlyOffice)]),
"push" => Some(vec![Service::Push(NotifyPush)]), ServiceType::Office => Some(vec![Service::Office(Office)]),
"smb" => Some(vec![Service::Smb(Smb)]), ServiceType::Push => Some(vec![Service::Push(NotifyPush)]),
"sharded" => Some(vec![Service::Sharding(Sharding)]), ServiceType::Smb => Some(vec![Service::Smb(Smb)]),
"sharding" => Some(vec![Service::Sharding(Sharding)]), ServiceType::Sharding => Some(vec![Service::Sharding(Sharding)]),
"single-shard" => Some(vec![Service::SingleShard(SingleShard)]), ServiceType::SingleShard => Some(vec![Service::SingleShard(SingleShard)]),
"singleshard" => Some(vec![Service::SingleShard(SingleShard)]), ServiceType::ShardingMigrate => {
"sharded-migrate" => Some(vec![Service::ShardingMigrate(ShardingMigrate)]), Some(vec![Service::ShardingMigrate(ShardingMigrate)])
"sharding-migrate" => Some(vec![Service::ShardingMigrate(ShardingMigrate)]), }
"sharded-migrate-unset" => { ServiceType::ShardingMigrateUnset => {
Some(vec![Service::ShardingMigrateUnset(ShardingMigrateUnset)]) Some(vec![Service::ShardingMigrateUnset(ShardingMigrateUnset)])
}
ServiceType::Dav => Some(vec![Service::Dav(Dav)]),
ServiceType::Sftp => Some(vec![Service::Sftp(Sftp)]),
ServiceType::Oc => Some(vec![Service::Oc(Oc)]),
ServiceType::Imaginary => Some(vec![Service::Imaginary(Imaginary)]),
ServiceType::Kaspersky => Some(vec![Service::Kaspersky(Kaspersky)]),
ServiceType::KasperskyIcap => Some(vec![Service::KasperskyIcap(KasperskyIcap)]),
ServiceType::ClamAv => Some(vec![Service::Clam(Clam)]),
ServiceType::ClamAvExternal => Some(vec![Service::ClamSocket(ClamSocket)]),
ServiceType::ClamAvSocket => Some(vec![Service::ClamSocket(ClamSocket)]),
ServiceType::ClamAvIcap => Some(vec![Service::ClamIcap(ClamIcap)]),
ServiceType::ClamAvIcapTls => Some(vec![Service::ClamIcapTls(ClamIcapTls)]),
ServiceType::Mail => Some(vec![Service::Mail(Mail)]),
ServiceType::Redis => Some(vec![Service::Redis(Redis)]),
ServiceType::RedisTls => Some(vec![Service::RedisTls(RedisTls)]),
} }
"sharding-migrate-unset" => { } else {
Some(vec![Service::ShardingMigrateUnset(ShardingMigrateUnset)]) presets
}
"dav" => Some(vec![Service::Dav(Dav)]),
"sftp" => Some(vec![Service::Sftp(Sftp)]),
"oc" => Some(vec![Service::Oc(Oc)]),
"imaginary" => Some(vec![Service::Imaginary(Imaginary)]),
"kaspersky" => Some(vec![Service::Kaspersky(Kaspersky)]),
"kaspersky-icap" => Some(vec![Service::KasperskyIcap(KasperskyIcap)]),
"clamav" => Some(vec![Service::Clam(Clam)]),
"clamav-external" => Some(vec![Service::ClamSocket(ClamSocket)]),
"clamav-socket" => Some(vec![Service::ClamSocket(ClamSocket)]),
"clamav-icap" => Some(vec![Service::ClamIcap(ClamIcap)]),
"clamav-icap-tls" => Some(vec![Service::ClamIcapTls(ClamIcapTls)]),
"clam" => Some(vec![Service::Clam(Clam)]),
"clam-external" => Some(vec![Service::ClamSocket(ClamSocket)]),
"clam-socket" => Some(vec![Service::ClamSocket(ClamSocket)]),
"clam-icap" => Some(vec![Service::ClamIcap(ClamIcap)]),
"clam-icap-tls" => Some(vec![Service::ClamIcapTls(ClamIcapTls)]),
"mail" => Some(vec![Service::Mail(Mail)]),
"redis" => Some(vec![Service::Redis(Redis)]),
"redis-tls" => Some(vec![Service::RedisTls(RedisTls)]),
_ => presets
.iter() .iter()
.find_map(|preset| (preset.name == ty).then(|| PresetService(preset.name.clone()))) .find_map(|preset| (preset.name == ty).then(|| PresetService(preset.name.clone())))
.map(Service::Preset) .map(Service::Preset)
.map(|service| vec![service]), .map(|service| vec![service])
} }
} }