From 13521ede8c09438a859a95f71ceaf379ee7f6902 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Sun, 14 Jan 2024 02:29:21 -0800 Subject: [PATCH] global justfile --- src/config.rs | 71 ++++++++++++++++++++++--------------- src/search.rs | 84 ++++++++++++++++++++++++++------------------ src/search_config.rs | 6 ++++ src/search_error.rs | 4 +++ 4 files changed, 102 insertions(+), 63 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0a0714635e..77828ef8d3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -99,6 +99,7 @@ mod arg { pub(crate) const DOTENV_PATH: &str = "DOTENV-PATH"; pub(crate) const DRY_RUN: &str = "DRY-RUN"; pub(crate) const DUMP_FORMAT: &str = "DUMP-FORMAT"; + pub(crate) const GLOBAL: &str = "GLOBAL"; pub(crate) const HIGHLIGHT: &str = "HIGHLIGHT"; pub(crate) const JUSTFILE: &str = "JUSTFILE"; pub(crate) const LIST_HEADING: &str = "LIST-HEADING"; @@ -404,7 +405,13 @@ impl Config { Arg::with_name(arg::ARGUMENTS) .multiple(true) .help("Overrides and recipe(s) to run, defaulting to the first recipe in the justfile"), - ); + ) + .arg( + Arg::with_name(arg::GLOBAL) + .long("global") + .short("g") + .help("Invoke using a global `justfile`") + ); if cfg!(feature = "help4help2man") { app.version(env!("CARGO_PKG_VERSION")).about(concat!( @@ -476,6 +483,38 @@ impl Config { } } + fn search_config(matches: &ArgMatches, positional: &Positional) -> ConfigResult { + if matches.is_present(arg::GLOBAL) { + return Ok(SearchConfig::Global); + } + + let justfile = matches.value_of(arg::JUSTFILE).map(PathBuf::from); + let working_directory = matches.value_of(arg::WORKING_DIRECTORY).map(PathBuf::from); + + if let Some(search_directory) = positional.search_directory.as_ref().map(PathBuf::from) { + if justfile.is_some() || working_directory.is_some() { + return Err(ConfigError::SearchDirConflict); + } + Ok(SearchConfig::FromSearchDirectory { search_directory }) + } else { + match (justfile, working_directory) { + (None, None) => Ok(SearchConfig::FromInvocationDirectory), + (Some(justfile), None) => Ok(SearchConfig::WithJustfile { justfile }), + (Some(justfile), Some(working_directory)) => { + Ok(SearchConfig::WithJustfileAndWorkingDirectory { + justfile, + working_directory, + }) + } + (None, Some(_)) => { + return Err(ConfigError::internal( + "--working-directory set without --justfile", + )) + } + } + } + } + pub(crate) fn from_matches(matches: &ArgMatches) -> ConfigResult { let invocation_directory = env::current_dir().context(config_error::CurrentDirContext)?; @@ -502,37 +541,11 @@ impl Config { let positional = Positional::from_values(matches.values_of(arg::ARGUMENTS)); - for (name, value) in positional.overrides { + for (name, value) in &positional.overrides { overrides.insert(name.clone(), value.clone()); } - let search_config = { - let justfile = matches.value_of(arg::JUSTFILE).map(PathBuf::from); - let working_directory = matches.value_of(arg::WORKING_DIRECTORY).map(PathBuf::from); - - if let Some(search_directory) = positional.search_directory.map(PathBuf::from) { - if justfile.is_some() || working_directory.is_some() { - return Err(ConfigError::SearchDirConflict); - } - SearchConfig::FromSearchDirectory { search_directory } - } else { - match (justfile, working_directory) { - (None, None) => SearchConfig::FromInvocationDirectory, - (Some(justfile), None) => SearchConfig::WithJustfile { justfile }, - (Some(justfile), Some(working_directory)) => { - SearchConfig::WithJustfileAndWorkingDirectory { - justfile, - working_directory, - } - } - (None, Some(_)) => { - return Err(ConfigError::internal( - "--working-directory set without --justfile", - )) - } - } - } - }; + let search_config = Self::search_config(matches, &positional)?; for subcommand in cmd::ARGLESS { if matches.is_present(subcommand) { diff --git a/src/search.rs b/src/search.rs index c14eb55354..2b6d82b40a 100644 --- a/src/search.rs +++ b/src/search.rs @@ -14,43 +14,50 @@ impl Search { search_config: &SearchConfig, invocation_directory: &Path, ) -> SearchResult { - match search_config { - SearchConfig::FromInvocationDirectory => Self::find_next(invocation_directory), - SearchConfig::FromSearchDirectory { search_directory } => { - let search_directory = Self::clean(invocation_directory, search_directory); + use SearchConfig::*; + Ok(match search_config { + FromInvocationDirectory => Self::find_next(invocation_directory)?, + FromSearchDirectory { search_directory } => { + let search_directory = Self::clean(invocation_directory, search_directory); let justfile = Self::justfile(&search_directory)?; - let working_directory = Self::working_directory_from_justfile(&justfile)?; - Ok(Self { + Self { justfile, working_directory, - }) + } } - SearchConfig::WithJustfile { justfile } => { + Global => { + let working_directory = Self::project_root(invocation_directory)?; + let home_dir = dirs::home_dir().ok_or(SearchError::MissingHomeDirectory)?; + let justfile = home_dir.join(DEFAULT_JUSTFILE_NAME); + Self { + working_directory, + justfile + } + }, + WithJustfile { justfile } => { let justfile = Self::clean(invocation_directory, justfile); - let working_directory = Self::working_directory_from_justfile(&justfile)?; - Ok(Self { + Self { justfile, working_directory, - }) + } } - SearchConfig::WithJustfileAndWorkingDirectory { + WithJustfileAndWorkingDirectory { justfile, working_directory, - } => Ok(Self { + } => Self { justfile: Self::clean(invocation_directory, justfile), working_directory: Self::clean(invocation_directory, working_directory), - }), - } + }, + }) } pub(crate) fn find_next(starting_dir: &Path) -> SearchResult { let justfile = Self::justfile(starting_dir)?; - let working_directory = Self::working_directory_from_justfile(&justfile)?; Ok(Self { @@ -63,50 +70,59 @@ impl Search { search_config: &SearchConfig, invocation_directory: &Path, ) -> SearchResult { - match search_config { - SearchConfig::FromInvocationDirectory => { - let working_directory = Self::project_root(invocation_directory)?; + use SearchConfig::*; + Ok(match search_config { + FromInvocationDirectory => { + let working_directory = Self::project_root(invocation_directory)?; let justfile = working_directory.join(DEFAULT_JUSTFILE_NAME); - Ok(Self { + Self { justfile, working_directory, - }) + } } - SearchConfig::FromSearchDirectory { search_directory } => { + FromSearchDirectory { search_directory } => { let search_directory = Self::clean(invocation_directory, search_directory); - let working_directory = Self::project_root(&search_directory)?; - let justfile = working_directory.join(DEFAULT_JUSTFILE_NAME); - Ok(Self { + Self { justfile, working_directory, - }) + } } - SearchConfig::WithJustfile { justfile } => { - let justfile = Self::clean(invocation_directory, justfile); + Global => { + let working_directory = Self::project_root(invocation_directory)?; + let home_dir = dirs::home_dir().ok_or(SearchError::MissingHomeDirectory)?; + let justfile = home_dir.join(DEFAULT_JUSTFILE_NAME); + + Self { + justfile, + working_directory, + } + } + WithJustfile { justfile } => { + let justfile = Self::clean(invocation_directory, justfile); let working_directory = Self::working_directory_from_justfile(&justfile)?; - Ok(Self { + Self { justfile, working_directory, - }) + } } - SearchConfig::WithJustfileAndWorkingDirectory { + WithJustfileAndWorkingDirectory { justfile, working_directory, - } => Ok(Self { + } => Self { justfile: Self::clean(invocation_directory, justfile), working_directory: Self::clean(invocation_directory, working_directory), - }), - } + }, + }) } pub(crate) fn justfile(directory: &Path) -> SearchResult { diff --git a/src/search_config.rs b/src/search_config.rs index 3af178a3bf..e975f7bd27 100644 --- a/src/search_config.rs +++ b/src/search_config.rs @@ -7,11 +7,17 @@ pub(crate) enum SearchConfig { /// to the root, setting the working directory to the directory in which the /// justfile is found. FromInvocationDirectory, + /// As in `Invocation`, but start from `search_directory`. FromSearchDirectory { search_directory: PathBuf }, + + /// Search for a justfile in a well-known global path + Global, + /// Use user-specified justfile, with the working directory set to the /// directory that contains it. WithJustfile { justfile: PathBuf }, + /// Use user-specified justfile and working directory. WithJustfileAndWorkingDirectory { justfile: PathBuf, diff --git a/src/search_error.rs b/src/search_error.rs index 3ee783ec79..37f3d34445 100644 --- a/src/search_error.rs +++ b/src/search_error.rs @@ -14,6 +14,10 @@ pub(crate) enum SearchError { }, #[snafu(display("Justfile path had no parent: {}", path.display()))] JustfileHadNoParent { path: PathBuf }, + + #[snafu(display("Home directory not found"))] + MissingHomeDirectory, + #[snafu(display( "Multiple candidate justfiles found in `{}`: {}", candidates.iter().next().unwrap().parent().unwrap().display(),