'new' now fails on duplicate names, reduced code reuse
This commit is contained in:
parent
21bd6f40ca
commit
b24778113a
4 changed files with 90 additions and 80 deletions
|
@ -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); }
|
||||
|
||||
|
|
13
src/error.rs
13
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<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");
|
||||
|
|
46
src/state.rs
46
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<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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
20
src/util.rs
20
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<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 }
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue