'new' now fails on duplicate names, reduced code reuse

This commit is contained in:
Valerie Wolfe 2024-07-01 11:52:33 -04:00
parent 21bd6f40ca
commit b24778113a
4 changed files with 90 additions and 80 deletions

View file

@ -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<commands::SelectWindow>;
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); }

View file

@ -6,6 +6,7 @@ pub fn no_subcommand(subcommand: String) {
exit(1);
}
/// target session not found; code 2
pub fn no_target<S: Into<String>>(target: S) {
let target = target.into();
@ -13,12 +14,14 @@ pub fn no_target<S: Into<String>>(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<S: Into<String>>(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");

View file

@ -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<String>,
pub title: Option<String>
pub title: Option<String>,
pub repository: Option<Repository>
}
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<String> { self.args.subcommand().unwrap_or(None) }
pub fn target_title(&mut self) -> Option<String> {
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<Repository> {
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 }
}
}

View file

@ -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<PathBuf> {
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<PathBuf> {
// 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 }
}