use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use sysinfo::{Pid, Process, ProcessExt, SystemExt};
use lazy_static::lazy_static;
#[derive(Clone, Debug, PartialEq)]
pub enum CallingProcess {
GitShow(String), // (extension)
GitGrep((HashSet<String>, HashSet<String>)), // ((long_options, short_options))
OtherGrep, // rg, grep, ag, ack, etc
}
pub fn calling_process() -> Option<Cow<'static, CallingProcess>> {
#[cfg(not(test))]
{
CACHED_CALLING_PROCESS
.as_ref()
.map(|proc| Cow::Borrowed(proc))
}
#[cfg(test)]
{
determine_calling_process().map(|proc| Cow::Owned(proc))
}
}
lazy_static! {
static ref CACHED_CALLING_PROCESS: Option<CallingProcess> = determine_calling_process();
}
fn determine_calling_process() -> Option<CallingProcess> {
calling_process_cmdline(ProcInfo::new(), describe_calling_process)
}
// Return value of `extract_args(args: &[String]) -> ProcessArgs<T>` function which is
// passed to `calling_process_cmdline()`.
#[derive(Debug, PartialEq)]
pub enum ProcessArgs<T> {
// A result has been successfully extracted from args.
Args(T),
// The extraction has failed.
ArgError,
// The process does not match, others may be inspected.
OtherProcess,
}
pub fn git_blame_filename_extension() -> Option<String> {
calling_process_cmdline(ProcInfo::new(), blame::guess_git_blame_filename_extension)
}
mod blame {
use super::*;
pub fn guess_git_blame_filename_extension(args: &[String]) -> ProcessArgs<String> {
let all_args = args.iter().map(|s| s.as_str());
// See git(1) and git-blame(1). Some arguments separate their parameter with space or '=', e.g.
// --date 2015 or --date=2015.
let git_blame_options_with_parameter =
"-C -c -L --since --ignore-rev --ignore-revs-file --contents --reverse --date";
let selected_args =
skip_uninteresting_args(all_args, git_blame_options_with_parameter.split(' '));
match selected_args.as_slice() {
[_git, "blame", .., last_arg] => match last_arg.split('.').last() {
Some(arg) => ProcessArgs::Args(arg.to_string()),
None => ProcessArgs::ArgError,
},
[_git, "blame"] => ProcessArgs::ArgError,
_ => ProcessArgs::OtherProcess,
}
}
}
pub fn describe_calling_process(args: &[String]) -> ProcessArgs<CallingProcess> {
let mut args = args.iter().map(|s| s.as_str());
match args.next() {
Some(command) => match Path::new(command).file_stem() {
Some(s) if s.to_str() == Some("git") => {
let mut args = args.skip_while(|s| *s != "grep" && *s != "show");
match args.next() {
Some("grep") => {
ProcessArgs::Args(CallingProcess::GitGrep(parse_command_option_keys(args)))
}
Some("show") => {
if let Some(extension) = get_git_show_file_extension(args) {
ProcessArgs::Args(CallingProcess::GitShow(extension.to_string()))
} else {
// It's git show, but we failed to determine the
// file extension. Don't look at any more processes.
ProcessArgs::ArgError
}
}
_ => {
// It's git, but not a subcommand that we parse. Don't
// look at any more processes.
ProcessArgs::ArgError
}
}
}
Some(s) => match s.to_str() {
Some("rg") | Some("grep") | Some("ack") | Some("ag") | Some("pt")
| Some("sift") | Some("ucg") => ProcessArgs::Args(CallingProcess::OtherGrep),
_ => {
// It's not git, and it's not another grep tool. Keep
// looking at other processes.
ProcessArgs::OtherProcess
}
},
_ => {
// Could not parse file stem (not expected); keep looking at
// other processes.
ProcessArgs::OtherProcess
}
},
_ => {
// Empty arguments (not expected); keep looking.
ProcessArgs::OtherProcess
}