refactor: update clap to v3 (#3370)

This commit is contained in:
David Knaack
2022-01-04 10:49:42 +01:00
committed by GitHub
parent 35eae3fb4a
commit 20cf200c3a
13 changed files with 394 additions and 401 deletions
+98 -44
View File
@@ -5,14 +5,17 @@ use crate::utils::{create_command, exec_timeout, CommandOutput};
use crate::modules;
use crate::utils::{self, home_dir};
use clap::ArgMatches;
use clap::Parser;
use git2::{ErrorCode::UnbornBranch, Repository, RepositoryState};
use once_cell::sync::OnceCell;
use std::collections::{HashMap, HashSet};
#[cfg(test)]
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
use std::ffi::{OsStr, OsString};
use std::fmt::Debug;
use std::fs;
use std::num::ParseIntError;
use std::path::{Path, PathBuf};
use std::string::String;
use std::time::{Duration, Instant};
@@ -37,7 +40,7 @@ pub struct Context<'a> {
dir_contents: OnceCell<DirContents>,
/// Properties to provide to modules.
pub properties: HashMap<&'a str, String>,
pub properties: Properties,
/// Pipestatus of processes in pipe
pub pipestatus: Option<Vec<String>>,
@@ -48,11 +51,8 @@ pub struct Context<'a> {
/// The shell the user is assumed to be running
pub shell: Shell,
/// Construct the right prompt instead of the left prompt
pub right: bool,
/// Construct the continuation prompt instead of the normal prompt
pub continuation: bool,
/// Which prompt to print (main, right, ...)
pub target: Target,
/// Width of terminal, or zero if width cannot be detected.
pub width: usize,
@@ -76,53 +76,46 @@ impl<'a> Context<'a> {
/// Identify the current working directory and create an instance of Context
/// for it. "logical-path" is used when a shell allows the "current working directory"
/// to be something other than a file system path (like powershell provider specific paths).
pub fn new(arguments: ArgMatches) -> Context {
pub fn new(arguments: Properties, target: Target) -> Context<'a> {
let shell = Context::get_shell();
// Retrieve the "current directory".
// If the path argument is not set fall back to the OS current directory.
let path = arguments
.value_of("path")
.map(PathBuf::from)
.path
.clone()
.or_else(|| env::current_dir().ok())
.or_else(|| env::var("PWD").map(PathBuf::from).ok())
.or_else(|| arguments.value_of("logical_path").map(PathBuf::from))
.or_else(|| arguments.logical_path.clone())
.unwrap_or_default();
// Retrieve the "logical directory".
// If the path argument is not set fall back to the PWD env variable set by many shells
// or to the other path.
let logical_path = arguments
.value_of("logical_path")
.map(PathBuf::from)
.logical_path
.clone()
.or_else(|| env::var("PWD").map(PathBuf::from).ok())
.unwrap_or_else(|| path.clone());
Context::new_with_shell_and_path(arguments, shell, path, logical_path)
Context::new_with_shell_and_path(arguments, shell, target, path, logical_path)
}
/// Create a new instance of Context for the provided directory
pub fn new_with_shell_and_path(
arguments: ArgMatches,
properties: Properties,
shell: Shell,
target: Target,
path: PathBuf,
logical_path: PathBuf,
) -> Context {
) -> Context<'a> {
let config = StarshipConfig::initialize();
// Unwrap the clap arguments into a simple hashtable
let properties: HashMap<&str, std::string::String> = arguments
.args
.iter()
.filter(|(_, v)| !v.vals.is_empty())
.map(|(a, b)| (*a, b.vals.first().cloned().unwrap().into_string().unwrap()))
.collect();
let pipestatus = arguments
.values_of("pipestatus")
let pipestatus = properties
.pipestatus
.as_deref()
.map(Context::get_and_flatten_pipestatus)
.flatten();
log::trace!("Received completed pipestatus of {:?}", pipestatus);
// Canonicalize the current path to resolve symlinks, etc.
@@ -136,14 +129,7 @@ impl<'a> Context<'a> {
.as_ref()
.map_or_else(StarshipRootConfig::default, StarshipRootConfig::load);
let right = arguments.is_present("right");
let continuation = arguments.is_present("continuation");
let width = arguments
.value_of("terminal_width")
.and_then(|w| w.parse().ok())
.or_else(|| terminal_size().map(|(w, _)| w.0 as usize))
.unwrap_or(80);
let width = properties.terminal_width;
Context {
config,
@@ -154,8 +140,7 @@ impl<'a> Context<'a> {
dir_contents: OnceCell::new(),
repo: OnceCell::new(),
shell,
right,
continuation,
target,
width,
#[cfg(test)]
env: HashMap::new(),
@@ -212,12 +197,13 @@ impl<'a> Context<'a> {
}
/// Reads and appropriately flattens multiple args for pipestatus
pub fn get_and_flatten_pipestatus(args: clap::Values) -> Option<Vec<String>> {
// TODO: Replace with value_delimiter = ' ' clap option?
pub fn get_and_flatten_pipestatus(args: &[String]) -> Option<Vec<String>> {
// Due to shell differences, we can potentially receive individual or space
// separated inputs, e.g. "0","1","2","0" is the same as "0 1 2 0" and
// "0 1", "2 0". We need to accept all these formats and return a Vec<String>
let parsed_vals = args
.into_iter()
.iter()
.map(|x| x.split_ascii_whitespace())
.flatten()
.map(|x| x.to_string())
@@ -310,8 +296,12 @@ impl<'a> Context<'a> {
}
}
// TODO: This should be used directly by clap parse
pub fn get_cmd_duration(&self) -> Option<u128> {
self.properties.get("cmd_duration")?.parse::<u128>().ok()
self.properties
.cmd_duration
.as_deref()
.and_then(|cd| cd.parse::<u128>().ok())
}
/// Execute a command and return the output on stdout and stderr if successful
@@ -574,6 +564,67 @@ pub enum Shell {
Unknown,
}
fn default_width() -> usize {
terminal_size().map_or(80, |(w, _)| w.0 as usize)
}
/// Which kind of prompt target to print (main prompt, rprompt, ...)
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Target {
Main,
Right,
Continuation,
}
/// Properties as passed on from the shell as arguments
#[derive(Parser, Debug)]
pub struct Properties {
/// The status code of the previously run command
#[clap(short = 's', long = "status")]
pub status_code: Option<i32>,
/// Bash and Zsh support returning codes for each process in a pipeline.
#[clap(long)]
pipestatus: Option<Vec<String>>,
/// The width of the current interactive terminal.
#[clap(short = 'w', long, default_value_t=default_width())]
terminal_width: usize,
/// The path that the prompt should render for.
#[clap(short, long)]
path: Option<PathBuf>,
/// The logical path that the prompt should render for.
/// This path should be a virtual/logical representation of the PATH argument.
#[clap(short = 'P', long)]
logical_path: Option<PathBuf>,
/// The execution duration of the last command, in milliseconds
#[clap(short = 'd', long)]
pub cmd_duration: Option<String>,
/// The keymap of fish/zsh
#[clap(short = 'k', long, default_value = "viins")]
pub keymap: String,
/// The number of currently running jobs
#[clap(short, long, default_value_t, parse(try_from_str=parse_jobs))]
pub jobs: i64,
}
impl Default for Properties {
fn default() -> Self {
Properties {
status_code: None,
pipestatus: None,
terminal_width: default_width(),
path: None,
logical_path: None,
cmd_duration: None,
keymap: "viins".to_string(),
jobs: 0,
}
}
}
fn parse_jobs(jobs: &str) -> Result<i64, ParseIntError> {
jobs.trim().parse::<i64>()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -660,8 +711,9 @@ mod tests {
// Mock navigation into the symlink path
let test_path = path_symlink.join("yyy");
let context = Context::new_with_shell_and_path(
ArgMatches::default(),
Default::default(),
Shell::Unknown,
Target::Main,
test_path.clone(),
test_path.clone(),
);
@@ -685,8 +737,9 @@ mod tests {
// Mock navigation to a directory which does not exist on disk
let test_path = Path::new("/path_which_does_not_exist").to_path_buf();
let context = Context::new_with_shell_and_path(
ArgMatches::default(),
Default::default(),
Shell::Unknown,
Target::Main,
test_path.clone(),
test_path.clone(),
);
@@ -705,8 +758,9 @@ mod tests {
// Mock navigation to a directory which does not exist on disk
let test_path = Path::new("~/path_which_does_not_exist").to_path_buf();
let context = Context::new_with_shell_and_path(
ArgMatches::default(),
Default::default(),
Shell::Unknown,
Target::Main,
test_path.clone(),
test_path.clone(),
);