From b24778113a9475d58afa519a448718d07a797d59 Mon Sep 17 00:00:00 2001 From: Valerie Date: Mon, 1 Jul 2024 11:52:33 -0400 Subject: [PATCH] 'new' now fails on duplicate names, reduced code reuse --- src/command/share.rs | 91 +++++++++++++++----------------------------- src/error.rs | 13 +++++++ src/state.rs | 46 ++++++++++++++++++++-- src/util.rs | 20 ++-------- 4 files changed, 90 insertions(+), 80 deletions(-) diff --git a/src/command/share.rs b/src/command/share.rs index e75b1ee..e7229e7 100644 --- a/src/command/share.rs +++ b/src/command/share.rs @@ -1,8 +1,5 @@ //! globally available tmux commands. -use std::{ - ffi::OsString, - process::exit -}; +use std::process::exit; use pico_args::{ Arguments, Error }; use termion::{ color, style }; @@ -27,18 +24,9 @@ pub fn attach(state: &mut State) { let read_only = state.flags.read_only; let detach_other = state.flags.detached; - let args = state.args.clone().finish(); - let target: String; - let window: Option<&OsString>; - if args.len() < 1 { - // missing name will attempt to fall back to repository - target = util::repo_fallback(); - if !util::session_exists(target.clone()) { error::missing_target(); } - window = None; - } else { - target = args.get(0).unwrap().to_string_lossy().to_string(); - window = args.get(1); - } + // consume arguments + let target = state.target_title().unwrap(); + let window = state.target(); // do not allow attaching to the same session if state.session && target == state.title.clone().unwrap() { error::same_session(); } @@ -56,7 +44,7 @@ pub fn attach(state: &mut State) { let select_window: Option; if let Some(window) = window { let mut command = commands::SelectWindow::new(); - command.target_window = Some(window.to_string_lossy()); + command.target_window = Some(window.into()); select_window = Some(command); } else { select_window = None; } @@ -69,34 +57,28 @@ pub fn attach(state: &mut State) { } pub fn context_action(state: &State) { - let repo = util::repo_root(std::env::current_dir().unwrap()); - if !state.session && repo.is_some() { - let target = util::repo_fallback(); - let mut args = Arguments::from_vec( vec![(&target).into()] ); - let mut substate = State::new(&mut args); - substate.flags = state.flags.clone(); - if util::session_exists(&target) { - attach(&mut substate); - } else { - new(&mut substate); + if !state.session { + if let Some(repository) = &state.repository { + let target = repository.name.clone(); + let mut args = Arguments::from_vec( vec![(&target).into()] ); + let mut substate = State::new(&mut args); + substate.flags = state.flags.clone(); + if util::session_exists(&target) { + attach(&mut substate); + } else { + new(&mut substate); + } + return; } - return; - } else { - // fallback behavior is list - list(&state); } + // fallback behavior is list + list(&state); } pub fn detach(state: &mut State) { util::terminal_enforce(); - // get target or fallback - let args = state.args.clone().finish(); - let target: String; - if args.len() < 1 { - target = util::repo_fallback(); - } else { - target = args.get(0).unwrap().to_string_lossy().to_string(); - } + + let target = state.target_title().unwrap(); // make sure the session exists let exists = util::session_exists(target.clone()); @@ -114,14 +96,8 @@ pub fn has(state: &mut State) { // consume optional flags let quiet = state.flags.quiet; - // get target or fallback - let args = state.args.clone().finish(); - let target: String; - if args.len() < 1 { - target = util::repo_fallback(); - } else { - target = args.get(0).unwrap().to_string_lossy().to_string(); - } + // get target + let target = state.target_title().unwrap(); // run command let success = util::session_exists(target.clone()); @@ -189,22 +165,17 @@ pub fn new(state: &mut State) { // get environment variables let window_name = env_var(env::NEW_WINDOW_NAME); - // get target or fallback - let args = state.args.clone().finish(); - let title: String; - let command: Option<&OsString>; - if args.len() < 1 { - // attempt repo fallback - title = util::repo_fallback(); - command = None; - } else { - title = args.get(0).unwrap().to_string_lossy().to_string(); - command = args.get(1); - } + // consume arguments + let title = state.target_title().unwrap(); + let command = state.target(); + + // don't allow duplicate names + let exists = util::session_exists(title.clone()); + if exists { error::target_exists(title.clone()); } let mut new = commands::NewSession::new(); new = new.session_name(title); - if let Some(command) = command { new.shell_command = Some(command.to_string_lossy()); } + if let Some(command) = command { new.shell_command = Some(command.into()); } if detached { new.detached = true; } if let Ok(target_dir) = target_dir { new = new.start_directory(target_dir); } diff --git a/src/error.rs b/src/error.rs index 44ec43c..9521ec6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ pub fn no_subcommand(subcommand: String) { exit(1); } + /// target session not found; code 2 pub fn no_target>(target: S) { let target = target.into(); @@ -13,12 +14,14 @@ pub fn no_target>(target: S) { exit(2); } + /// help topic doesn't exist; code 3 pub fn no_help(topic: String) { println!("remux: no help for \"{topic}\""); exit(3); } + /// user provided no target; code 4 pub fn missing_target() { println!("remux: no target provided"); @@ -31,12 +34,21 @@ pub fn same_session() { exit(4); } +/// a session with the target name already exists; code 4 +pub fn target_exists>(target: S) { + let target = target.into(); + println!("remux: session \"{target}\" already exists"); + exit(4); +} + + /// non-terminal environment prevention; code 5 pub fn not_terminal() { println!("remux: not running from a terminal"); exit(5); } + /// tried to nest while not in a session; code 6 pub fn not_nesting() { println!("remux: inappropriate nesting flag (-n); not in a session"); @@ -56,6 +68,7 @@ pub fn conflict_nest(reason: Option<&'static str>) { exit(6); } + /// tried to run a session command outside a session; code 7 pub fn not_in_session(cmd: &'static str) { println!("remux: '{cmd}' must be run from within a session"); diff --git a/src/state.rs b/src/state.rs index dec9cb8..f14938f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,4 +1,7 @@ -use std::env; +use std::{ + env, + path::PathBuf +}; use pico_args::Arguments; @@ -6,7 +9,7 @@ use crate::{ env::TMUX, error, flag::Flags, - util::session_name + util::{ find, session_name } }; pub struct State<'a> { @@ -15,7 +18,9 @@ pub struct State<'a> { pub session: bool, tmux_var: Option, - pub title: Option + pub title: Option, + + pub repository: Option } impl State<'_> { @@ -25,13 +30,17 @@ impl State<'_> { let tmux_var = env::var(TMUX).ok(); let session = tmux_var.is_some(); let title = if session { session_name() } else { None }; + let repository = Repository::find(); State { args, flags, + session, tmux_var, - title + title, + + repository } } @@ -54,5 +63,34 @@ impl State<'_> { } pub fn target(&mut self) -> Option { self.args.subcommand().unwrap_or(None) } + pub fn target_title(&mut self) -> Option { + let from_args = self.target(); + if from_args.is_some() { return from_args; } + else if let Some(repository) = &self.repository { Some(repository.name.clone()) } + else { + error::missing_target(); + None + } + } +} + + +pub struct Repository { + pub path: PathBuf, + pub name: String +} + +impl Repository { + pub fn find() -> Option { + let path = find(".git", env::current_dir().unwrap()); + if let Some(path) = path { + let name = path.file_name().unwrap().to_string_lossy().to_string(); + let inner = Repository { + path, + name + }; + Some(inner) + } else { None } + } } diff --git a/src/util.rs b/src/util.rs index 397617b..c215717 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,4 @@ use std::{ - env::current_dir, io::{ stdout, IsTerminal }, path::PathBuf }; @@ -48,23 +47,12 @@ pub fn terminal_enforce() { if !stdout().is_terminal() { error::not_terminal(); } } -/// attempt to return the repo name or exit -pub fn repo_fallback() -> String { - let repo = repo_root(current_dir().unwrap()); - if repo.is_none() { error::missing_target(); } +/// recursively propagate up directories to find a child +pub fn find(target: &'static str, path: PathBuf) -> Option { + if path.join(target).exists() { return Some(path); } - let target = repo.unwrap().file_name().unwrap().to_string_lossy().to_string(); - target -} - -/// recursively attempt to find a git root directory -pub fn repo_root(path: PathBuf) -> Option { - // if .git dir is found, return - if path.join(".git").exists() { return Some(path); } - - // otherwise, attempt to traverse let parent = path.parent(); - if let Some(parent) = parent { repo_root(parent.to_path_buf()) } + if let Some(parent) = parent { return find(target, parent.to_path_buf()) } else { None } }