Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find justfiles named .justfile #931

Merged
merged 3 commits into from
Jul 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
6 changes: 5 additions & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down Expand Up @@ -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:
Expand Down
33 changes: 18 additions & 15 deletions src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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 });
}
Expand Down Expand Up @@ -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;
Expand All @@ -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()) {
Expand All @@ -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)| {
Expand All @@ -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");
Expand All @@ -283,20 +286,20 @@ 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");
fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory");
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),
Expand Down
14 changes: 7 additions & 7 deletions src/search_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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`"
);
}
}
2 changes: 1 addition & 1 deletion src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
41 changes: 41 additions & 0 deletions tests/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
27 changes: 20 additions & 7 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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())
Expand Down