Support .justfile
as an alternative to justfile
(#931)
This commit is contained in:
parent
0662e4c042
commit
9c3bbc9fa7
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -549,9 +549,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "temptree"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13f60523942b252a93f18dd6c8ba53488929d59f7b106be23a29bc9cbc466461"
|
||||
checksum = "8fda94d8251b40088cb769576f436da19ac1d1ae792c97d0afe1cadc890c8630"
|
||||
dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
@ -49,7 +49,7 @@ cradle = "0.0.13"
|
||||
executable-path = "1.0.0"
|
||||
pretty_assertions = "0.7.0"
|
||||
regex = "1.5.4"
|
||||
temptree = "0.1.0"
|
||||
temptree = "0.2.0"
|
||||
which = "4.0.0"
|
||||
yaml-rust = "0.4.5"
|
||||
|
||||
|
@ -231,7 +231,7 @@ another-recipe:
|
||||
|
||||
When you invoke `just` it looks for file `justfile` in the current directory and upwards, so you can invoke it from any subdirectory of your project.
|
||||
|
||||
The search for a `justfile` is case insensitive, so any case, like `Justfile`, `JUSTFILE`, or `JuStFiLe`, will work.
|
||||
The search for a `justfile` is case insensitive, so any case, like `Justfile`, `JUSTFILE`, or `JuStFiLe`, will work. `just` will also look for files with the name `.justfile`, in case you'd like to hide a `justfile`.
|
||||
|
||||
Running `just` with no arguments runs the first recipe in the `justfile`:
|
||||
|
||||
@ -1542,6 +1542,10 @@ $ just foo/build
|
||||
$ just foo/
|
||||
```
|
||||
|
||||
=== Hiding Justfiles
|
||||
|
||||
`just` looks for justfiles named `justfile` and `.justfile`, which can be used to keep a `justfile` hidden.
|
||||
|
||||
=== Just Scripts
|
||||
|
||||
By adding a shebang line to the top of a justfile and making it executable, `just` can be used as an interpreter for scripts:
|
||||
|
@ -2,7 +2,8 @@ use crate::common::*;
|
||||
|
||||
use std::path::Component;
|
||||
|
||||
pub(crate) const FILENAME: &str = "justfile";
|
||||
const DEFAULT_JUSTFILE_NAME: &str = JUSTFILE_NAMES[0];
|
||||
const JUSTFILE_NAMES: &[&str] = &["justfile", ".justfile"];
|
||||
const PROJECT_ROOT_CHILDREN: &[&str] = &[".bzr", ".git", ".hg", ".svn", "_darcs"];
|
||||
|
||||
pub(crate) struct Search {
|
||||
@ -69,7 +70,7 @@ impl Search {
|
||||
SearchConfig::FromInvocationDirectory => {
|
||||
let working_directory = Self::project_root(&invocation_directory)?;
|
||||
|
||||
let justfile = working_directory.join(FILENAME);
|
||||
let justfile = working_directory.join(DEFAULT_JUSTFILE_NAME);
|
||||
|
||||
Ok(Self {
|
||||
justfile,
|
||||
@ -82,7 +83,7 @@ impl Search {
|
||||
|
||||
let working_directory = Self::project_root(&search_directory)?;
|
||||
|
||||
let justfile = working_directory.join(FILENAME);
|
||||
let justfile = working_directory.join(DEFAULT_JUSTFILE_NAME);
|
||||
|
||||
Ok(Self {
|
||||
justfile,
|
||||
@ -113,7 +114,7 @@ impl Search {
|
||||
|
||||
fn justfile(directory: &Path) -> SearchResult<PathBuf> {
|
||||
for directory in directory.ancestors() {
|
||||
let mut candidates = Vec::new();
|
||||
let mut candidates = BTreeSet::new();
|
||||
|
||||
let entries = fs::read_dir(directory).map_err(|io_error| SearchError::Io {
|
||||
io_error,
|
||||
@ -125,14 +126,16 @@ impl Search {
|
||||
directory: directory.to_owned(),
|
||||
})?;
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
if name.eq_ignore_ascii_case(FILENAME) {
|
||||
candidates.push(entry.path());
|
||||
for justfile_name in JUSTFILE_NAMES {
|
||||
if name.eq_ignore_ascii_case(justfile_name) {
|
||||
candidates.insert(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if candidates.len() == 1 {
|
||||
return Ok(candidates.pop().unwrap());
|
||||
return Ok(candidates.into_iter().next().unwrap());
|
||||
} else if candidates.len() > 1 {
|
||||
return Err(SearchError::MultipleCandidates { candidates });
|
||||
}
|
||||
@ -212,10 +215,10 @@ mod tests {
|
||||
fn multiple_candidates() {
|
||||
let tmp = testing::tempdir();
|
||||
let mut path = tmp.path().to_path_buf();
|
||||
path.push(FILENAME);
|
||||
path.push(DEFAULT_JUSTFILE_NAME);
|
||||
fs::write(&path, "default:\n\techo ok").unwrap();
|
||||
path.pop();
|
||||
path.push(FILENAME.to_uppercase());
|
||||
path.push(DEFAULT_JUSTFILE_NAME.to_uppercase());
|
||||
if fs::File::open(path.as_path()).is_ok() {
|
||||
// We are in case-insensitive file system
|
||||
return;
|
||||
@ -232,7 +235,7 @@ mod tests {
|
||||
fn found() {
|
||||
let tmp = testing::tempdir();
|
||||
let mut path = tmp.path().to_path_buf();
|
||||
path.push(FILENAME);
|
||||
path.push(DEFAULT_JUSTFILE_NAME);
|
||||
fs::write(&path, "default:\n\techo ok").unwrap();
|
||||
path.pop();
|
||||
if let Err(err) = Search::justfile(path.as_path()) {
|
||||
@ -244,7 +247,7 @@ mod tests {
|
||||
fn found_spongebob_case() {
|
||||
let tmp = testing::tempdir();
|
||||
let mut path = tmp.path().to_path_buf();
|
||||
let spongebob_case = FILENAME
|
||||
let spongebob_case = DEFAULT_JUSTFILE_NAME
|
||||
.chars()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
@ -267,7 +270,7 @@ mod tests {
|
||||
fn found_from_inner_dir() {
|
||||
let tmp = testing::tempdir();
|
||||
let mut path = tmp.path().to_path_buf();
|
||||
path.push(FILENAME);
|
||||
path.push(DEFAULT_JUSTFILE_NAME);
|
||||
fs::write(&path, "default:\n\techo ok").unwrap();
|
||||
path.pop();
|
||||
path.push("a");
|
||||
@ -283,12 +286,12 @@ mod tests {
|
||||
fn found_and_stopped_at_first_justfile() {
|
||||
let tmp = testing::tempdir();
|
||||
let mut path = tmp.path().to_path_buf();
|
||||
path.push(FILENAME);
|
||||
path.push(DEFAULT_JUSTFILE_NAME);
|
||||
fs::write(&path, "default:\n\techo ok").unwrap();
|
||||
path.pop();
|
||||
path.push("a");
|
||||
fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory");
|
||||
path.push(FILENAME);
|
||||
path.push(DEFAULT_JUSTFILE_NAME);
|
||||
fs::write(&path, "default:\n\techo ok").unwrap();
|
||||
path.pop();
|
||||
path.push("b");
|
||||
@ -296,7 +299,7 @@ mod tests {
|
||||
match Search::justfile(path.as_path()) {
|
||||
Ok(found_path) => {
|
||||
path.pop();
|
||||
path.push(FILENAME);
|
||||
path.push(DEFAULT_JUSTFILE_NAME);
|
||||
assert_eq!(found_path, path);
|
||||
},
|
||||
Err(err) => panic!("No errors were expected: {}", err),
|
||||
|
@ -16,14 +16,14 @@ pub(crate) enum SearchError {
|
||||
JustfileHadNoParent { path: PathBuf },
|
||||
#[snafu(display(
|
||||
"Multiple candidate justfiles found in `{}`: {}",
|
||||
candidates[0].parent().unwrap().display(),
|
||||
candidates.iter().next().unwrap().parent().unwrap().display(),
|
||||
List::and_ticked(
|
||||
candidates
|
||||
.iter()
|
||||
.map(|candidate| candidate.file_name().unwrap().to_string_lossy())
|
||||
),
|
||||
))]
|
||||
MultipleCandidates { candidates: Vec<PathBuf> },
|
||||
MultipleCandidates { candidates: BTreeSet<PathBuf> },
|
||||
#[snafu(display("No justfile found"))]
|
||||
NotFound,
|
||||
}
|
||||
@ -35,15 +35,15 @@ mod tests {
|
||||
#[test]
|
||||
fn multiple_candidates_formatting() {
|
||||
let error = SearchError::MultipleCandidates {
|
||||
candidates: vec![
|
||||
PathBuf::from("/foo/justfile"),
|
||||
PathBuf::from("/foo/JUSTFILE"),
|
||||
],
|
||||
candidates: [Path::new("/foo/justfile"), Path::new("/foo/JUSTFILE")]
|
||||
.iter()
|
||||
.map(|path| path.to_path_buf())
|
||||
.collect(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"Multiple candidate justfiles found in `/foo`: `justfile` and `JUSTFILE`"
|
||||
"Multiple candidate justfiles found in `/foo`: `JUSTFILE` and `justfile`"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ pub(crate) fn config(args: &[&str]) -> Config {
|
||||
|
||||
pub(crate) fn search(config: &Config) -> Search {
|
||||
let working_directory = config.invocation_directory.clone();
|
||||
let justfile = working_directory.join(crate::search::FILENAME);
|
||||
let justfile = working_directory.join("justfile");
|
||||
|
||||
Search {
|
||||
justfile,
|
||||
|
@ -18,7 +18,7 @@ pub(crate) use libc::{EXIT_FAILURE, EXIT_SUCCESS};
|
||||
pub(crate) use pretty_assertions::Comparison;
|
||||
pub(crate) use regex::Regex;
|
||||
pub(crate) use tempfile::TempDir;
|
||||
pub(crate) use temptree::temptree;
|
||||
pub(crate) use temptree::{temptree, tree, Tree};
|
||||
pub(crate) use which::which;
|
||||
pub(crate) use yaml_rust::YamlLoader;
|
||||
|
||||
|
@ -142,3 +142,44 @@ fn single_upwards() {
|
||||
|
||||
search_test(&path, &["../"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_dot_justfile() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
echo bad
|
||||
",
|
||||
)
|
||||
.tree(tree! {
|
||||
dir: {
|
||||
".justfile": "
|
||||
foo:
|
||||
echo ok
|
||||
"
|
||||
}
|
||||
})
|
||||
.current_dir("dir")
|
||||
.stderr("echo ok\n")
|
||||
.stdout("ok\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_justfile_conflicts_with_justfile() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
",
|
||||
)
|
||||
.tree(tree! {
|
||||
".justfile": "
|
||||
foo:
|
||||
",
|
||||
})
|
||||
.stderr_regex("error: Multiple candidate justfiles found in `.*`: `.justfile` and `justfile`\n")
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
@ -33,17 +33,18 @@ macro_rules! test {
|
||||
}
|
||||
|
||||
pub(crate) struct Test {
|
||||
pub(crate) tempdir: TempDir,
|
||||
pub(crate) justfile: Option<String>,
|
||||
pub(crate) args: Vec<String>,
|
||||
pub(crate) current_dir: PathBuf,
|
||||
pub(crate) env: BTreeMap<String, String>,
|
||||
pub(crate) stdin: String,
|
||||
pub(crate) stdout: String,
|
||||
pub(crate) justfile: Option<String>,
|
||||
pub(crate) shell: bool,
|
||||
pub(crate) status: i32,
|
||||
pub(crate) stderr: String,
|
||||
pub(crate) stderr_regex: Option<Regex>,
|
||||
pub(crate) status: i32,
|
||||
pub(crate) shell: bool,
|
||||
pub(crate) stdin: String,
|
||||
pub(crate) stdout: String,
|
||||
pub(crate) suppress_dotenv_load_warning: bool,
|
||||
pub(crate) tempdir: TempDir,
|
||||
}
|
||||
|
||||
impl Test {
|
||||
@ -54,6 +55,7 @@ impl Test {
|
||||
pub(crate) fn with_tempdir(tempdir: TempDir) -> Self {
|
||||
Self {
|
||||
args: Vec::new(),
|
||||
current_dir: PathBuf::new(),
|
||||
env: BTreeMap::new(),
|
||||
justfile: Some(String::new()),
|
||||
shell: true,
|
||||
@ -79,6 +81,11 @@ impl Test {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn current_dir(mut self, path: impl AsRef<Path>) -> Self {
|
||||
self.current_dir = path.as_ref().to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn env(mut self, key: &str, val: &str) -> Self {
|
||||
self.env.insert(key.to_string(), val.to_string());
|
||||
self
|
||||
@ -132,6 +139,12 @@ impl Test {
|
||||
self.suppress_dotenv_load_warning = suppress_dotenv_load_warning;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn tree(self, mut tree: Tree) -> Self {
|
||||
tree.map(|_name, content| unindent(content));
|
||||
tree.instantiate(&self.tempdir.path()).unwrap();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Test {
|
||||
@ -165,7 +178,7 @@ impl Test {
|
||||
"0"
|
||||
},
|
||||
)
|
||||
.current_dir(self.tempdir.path())
|
||||
.current_dir(self.tempdir.path().join(self.current_dir))
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
|
Loading…
Reference in New Issue
Block a user