aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs144
-rw-r--r--src/main.rs1
-rw-r--r--src/session.rs79
3 files changed, 181 insertions, 43 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 138815fa..13482e69 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -339,6 +339,7 @@ fn run_session_command(args: &[String]) -> std::io::Result<i32> {
match subcommand {
"list" => session_list(&args[1..]),
+ "attach" => session_attach_help(&args[1..]),
"stop" => session_stop(&args[1..]),
"delete" => session_delete(&args[1..]),
"help" | "--help" | "-h" => {
@@ -373,30 +374,43 @@ fn server_reload_config(args: &[String]) -> std::io::Result<i32> {
})?)
}
-fn session_list(args: &[String]) -> std::io::Result<i32> {
- if !args.is_empty() {
- eprintln!("usage: herdr session list");
- return Ok(2);
+fn session_attach_help(args: &[String]) -> std::io::Result<i32> {
+ if matches!(
+ args.first().map(String::as_str),
+ Some("help" | "--help" | "-h")
+ ) {
+ eprintln!("usage: herdr session attach <name>");
+ return Ok(0);
}
+ eprintln!("usage: herdr session attach <name>");
+ Ok(2)
+}
+
+fn session_list(args: &[String]) -> std::io::Result<i32> {
+ let json = match parse_session_json_only(args, "usage: herdr session list [--json]") {
+ Ok(json) => json,
+ Err(code) => return Ok(code),
+ };
let sessions = crate::session::list_sessions()?;
- _print_json(&serde_json::json!({
- "sessions": sessions,
- }));
+ if json {
+ _print_json(&serde_json::json!({
+ "sessions": sessions,
+ }));
+ } else {
+ print_session_table(&sessions);
+ }
Ok(0)
}
fn session_stop(args: &[String]) -> std::io::Result<i32> {
- let Some(name) = args.first() else {
- eprintln!("usage: herdr session stop <name>");
- return Ok(2);
- };
- if args.len() != 1 {
- eprintln!("usage: herdr session stop <name>");
- return Ok(2);
- }
+ let (name, json) =
+ match parse_session_name_and_json(args, "usage: herdr session stop <name> [--json]") {
+ Ok(parsed) => parsed,
+ Err(code) => return Ok(code),
+ };
- let target = match crate::session::parse_target_name(name) {
+ let target = match crate::session::parse_target_name(&name) {
Ok(target) => target,
Err(message) => {
print_session_error("invalid_session_name", &message);
@@ -405,10 +419,14 @@ fn session_stop(args: &[String]) -> std::io::Result<i32> {
};
match crate::session::stop_session(target.as_deref()) {
Ok(session) => {
- _print_json(&serde_json::json!({
- "stopped": true,
- "session": session,
- }));
+ if json {
+ _print_json(&serde_json::json!({
+ "stopped": true,
+ "session": session,
+ }));
+ } else {
+ println!("stopped session {}", session.name);
+ }
Ok(0)
}
Err(message) => {
@@ -419,21 +437,22 @@ fn session_stop(args: &[String]) -> std::io::Result<i32> {
}
fn session_delete(args: &[String]) -> std::io::Result<i32> {
- let Some(name) = args.first() else {
- eprintln!("usage: herdr session delete <name>");
- return Ok(2);
- };
- if args.len() != 1 {
- eprintln!("usage: herdr session delete <name>");
- return Ok(2);
- }
+ let (name, json) =
+ match parse_session_name_and_json(args, "usage: herdr session delete <name> [--json]") {
+ Ok(parsed) => parsed,
+ Err(code) => return Ok(code),
+ };
- match crate::session::delete_session(name) {
+ match crate::session::delete_session(&name) {
Ok(session) => {
- _print_json(&serde_json::json!({
- "deleted": true,
- "session": session,
- }));
+ if json {
+ _print_json(&serde_json::json!({
+ "deleted": true,
+ "session": session,
+ }));
+ } else {
+ println!("deleted session {}", session.name);
+ }
Ok(0)
}
Err(message) => {
@@ -1317,6 +1336,58 @@ fn parse_u64_flag(flag: &str, value: &str) -> std::io::Result<u64> {
.map_err(|_| std::io::Error::other(format!("invalid value for {flag}: {value}")))
}
+fn parse_session_json_only(args: &[String], usage: &str) -> Result<bool, i32> {
+ match args {
+ [] => Ok(false),
+ [flag] if flag == "--json" => Ok(true),
+ _ => {
+ eprintln!("{usage}");
+ Err(2)
+ }
+ }
+}
+
+fn parse_session_name_and_json(args: &[String], usage: &str) -> Result<(String, bool), i32> {
+ let mut name = None;
+ let mut json = false;
+ for arg in args {
+ if arg == "--json" {
+ json = true;
+ } else if name.is_none() {
+ name = Some(arg.clone());
+ } else {
+ eprintln!("{usage}");
+ return Err(2);
+ }
+ }
+
+ let Some(name) = name else {
+ eprintln!("{usage}");
+ return Err(2);
+ };
+ Ok((name, json))
+}
+
+fn print_session_table(sessions: &[crate::session::SessionInfo]) {
+ println!(
+ "{:<20} {:<8} {:<48} {}",
+ "name", "status", "directory", "socket"
+ );
+ for session in sessions {
+ println!(
+ "{:<20} {:<8} {:<48} {}",
+ session.name,
+ if session.running {
+ "running"
+ } else {
+ "stopped"
+ },
+ session.session_dir,
+ session.socket_path
+ );
+ }
+}
+
fn print_session_error(code: &str, message: &str) {
eprintln!(
"{}",
@@ -1400,9 +1471,10 @@ fn print_integration_help() {
fn print_session_help() {
eprintln!("herdr session commands:");
- eprintln!(" herdr session list");
- eprintln!(" herdr session stop <name>");
- eprintln!(" herdr session delete <name>");
+ eprintln!(" herdr session list [--json]");
+ eprintln!(" herdr session attach <name>");
+ eprintln!(" herdr session stop <name> [--json]");
+ eprintln!(" herdr session delete <name> [--json]");
eprintln!(" use 'default' as <name> to target the default session for stop");
}
diff --git a/src/main.rs b/src/main.rs
index cf5842c4..d0a535da 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -200,6 +200,7 @@ fn main() -> io::Result<()> {
println!();
println!("Usage: herdr [options]");
println!(" herdr --session <name> [options]");
+ println!(" herdr session attach <name>");
println!(" herdr update");
println!(" herdr server stop");
println!(" herdr server reload-config");
diff --git a/src/session.rs b/src/session.rs
index feeb9740..b164fecc 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -28,6 +28,25 @@ pub fn configure_from_args(args: &[String]) -> Result<Vec<String>, String> {
cleaned.push(program.clone());
}
+ if args.get(1).map(String::as_str) == Some("session")
+ && args.get(2).map(String::as_str) == Some("attach")
+ {
+ if matches!(
+ args.get(3).map(String::as_str),
+ Some("help" | "--help" | "-h")
+ ) {
+ return Ok(args.to_vec());
+ }
+ let Some(name) = args.get(3) else {
+ return Err("usage: herdr session attach <name>".to_string());
+ };
+ if args.len() != 4 {
+ return Err("usage: herdr session attach <name>".to_string());
+ }
+ apply_explicit_name(name)?;
+ return Ok(cleaned);
+ }
+
let mut requested_session = None;
let mut index = 1;
while index < args.len() {
@@ -51,13 +70,7 @@ pub fn configure_from_args(args: &[String]) -> Result<Vec<String>, String> {
}
if let Some(session) = requested_session {
- let session = normalize_name(&session)?;
- if let Some(session) = session {
- std::env::set_var(SESSION_ENV_VAR, session);
- } else {
- std::env::remove_var(SESSION_ENV_VAR);
- }
- EXPLICIT_SESSION_REQUESTED.store(true, Ordering::Relaxed);
+ apply_explicit_name(&session)?;
} else if std::env::var_os(crate::api::SOCKET_PATH_ENV_VAR).is_some() {
EXPLICIT_SESSION_REQUESTED.store(false, Ordering::Relaxed);
} else if let Ok(session) = std::env::var(SESSION_ENV_VAR) {
@@ -264,6 +277,17 @@ pub fn validate_name(name: &str) -> Result<(), String> {
Ok(())
}
+fn apply_explicit_name(name: &str) -> Result<(), String> {
+ let session = normalize_name(name)?;
+ if let Some(session) = session {
+ std::env::set_var(SESSION_ENV_VAR, session);
+ } else {
+ std::env::remove_var(SESSION_ENV_VAR);
+ }
+ EXPLICIT_SESSION_REQUESTED.store(true, Ordering::Relaxed);
+ Ok(())
+}
+
fn normalize_name(name: &str) -> Result<Option<String>, String> {
if name == DEFAULT_SESSION_NAME {
return Ok(None);
@@ -326,6 +350,47 @@ mod tests {
}
#[test]
+ fn configure_from_args_rewrites_session_attach_to_default_launch() {
+ let _guard = env_lock().lock().unwrap();
+ std::env::set_var(SESSION_ENV_VAR, "bad/name");
+ std::env::set_var(crate::api::SOCKET_PATH_ENV_VAR, "/tmp/inherited.sock");
+ clear_explicit_session_for_test();
+ let args = vec![
+ "herdr".to_string(),
+ "session".to_string(),
+ "attach".to_string(),
+ "work".to_string(),
+ ];
+
+ let cleaned = configure_from_args(&args).unwrap();
+
+ assert_eq!(std::env::var(SESSION_ENV_VAR).as_deref(), Ok("work"));
+ assert!(explicit_session_requested());
+ assert_eq!(cleaned, vec!["herdr"]);
+ std::env::remove_var(SESSION_ENV_VAR);
+ std::env::remove_var(crate::api::SOCKET_PATH_ENV_VAR);
+ clear_explicit_session_for_test();
+ }
+
+ #[test]
+ fn configure_from_args_leaves_session_attach_help_for_cli_dispatch() {
+ let _guard = env_lock().lock().unwrap();
+ std::env::remove_var(SESSION_ENV_VAR);
+ clear_explicit_session_for_test();
+ let args = vec![
+ "herdr".to_string(),
+ "session".to_string(),
+ "attach".to_string(),
+ "-h".to_string(),
+ ];
+
+ let cleaned = configure_from_args(&args).unwrap();
+
+ assert_eq!(cleaned, args);
+ assert!(!explicit_session_requested());
+ }
+
+ #[test]
fn configure_from_args_maps_default_session_name_to_default_path() {
let _guard = env_lock().lock().unwrap();
let config_home =