Allow path-prefixed first arguments (#139)

If the first argument to just contains a `/`, then it will be handled
specially. Everything before the last `/` is treated as a directory, and
just will search for the justfile starting there, instead of in the
current directory.
This commit is contained in:
Casey Rodarmor 2016-12-30 00:09:35 -08:00 committed by GitHub
parent 86755582bb
commit b267d0444e
3 changed files with 112 additions and 26 deletions

View file

@ -146,6 +146,22 @@ cc main.c foo.c bar.c -o main
testing... all tests passed!
```
If the first argument passed to `just` contains a `/`, then the following occurs:
1. The argument is split at the last `/`.
2. The part before the last `/` is treated as a directory. Just will start its search for the justfile there, instead of the in current directory.
3. The part after the last slash is treated as a normal argument, or ignored if it is empty.
This may seem a little strange, but it's useful if you wish to run a command in a justfile that is in a subdirectory.
For example, if you are in a directory which contains a subdirectory named `foo`, which contains justfile with the recipe `build`, which is also the default recipe, the following are all equivalent:
```sh
$ (cd foo && just build)
$ just foo/build
$ just foo/
```
Assignment, strings, concatination, and substitution with `{{...}}` are supported:
```make

View file

@ -163,6 +163,52 @@ pub fn app() {
None => die!("Invalid argument to --color. This is a bug in just."),
};
let set_count = matches.occurrences_of("set");
let mut overrides = BTreeMap::new();
if set_count > 0 {
let mut values = matches.values_of("set").unwrap();
for _ in 0..set_count {
overrides.insert(values.next().unwrap(), values.next().unwrap());
}
}
let override_re = regex::Regex::new("^([^=]+)=(.*)$").unwrap();
let raw_arguments = matches.values_of("arguments").map(|values| values.collect::<Vec<_>>())
.unwrap_or_default();
for argument in raw_arguments.iter().take_while(|arg| override_re.is_match(arg)) {
let captures = override_re.captures(argument).unwrap();
overrides.insert(captures.at(1).unwrap(), captures.at(2).unwrap());
}
let rest = raw_arguments.iter().skip_while(|arg| override_re.is_match(arg))
.enumerate()
.flat_map(|(i, argument)| {
if i == 0 {
if let Some(i) = argument.rfind("/") {
if matches.is_present("working-directory") {
die!("--working-directory and a path prefixed recipe may not be used together.");
}
let (dir, recipe) = argument.split_at(i + 1);
if let Err(error) = env::set_current_dir(dir) {
die!("Error changing directory: {}", error);
}
if recipe.is_empty() {
return None;
} else {
return Some(recipe);
}
}
}
Some(*argument)
})
.collect::<Vec<&str>>();
let justfile_option = matches.value_of("justfile");
let working_directory_option = matches.value_of("working-directory");
@ -275,28 +321,6 @@ pub fn app() {
}
}
let set_count = matches.occurrences_of("set");
let mut overrides = BTreeMap::new();
if set_count > 0 {
let mut values = matches.values_of("set").unwrap();
for _ in 0..set_count {
overrides.insert(values.next().unwrap(), values.next().unwrap());
}
}
let override_re = regex::Regex::new("^([^=]+)=(.*)$").unwrap();
let raw_arguments = matches.values_of("arguments").map(|values| values.collect::<Vec<_>>())
.unwrap_or_default();
for argument in raw_arguments.iter().take_while(|arg| override_re.is_match(arg)) {
let captures = override_re.captures(argument).unwrap();
overrides.insert(captures.at(1).unwrap(), captures.at(2).unwrap());
}
let rest = raw_arguments.iter().skip_while(|arg| override_re.is_match(arg))
.cloned().collect::<Vec<_>>();
let arguments = if !rest.is_empty() {
rest
} else if let Some(recipe) = justfile.first() {

View file

@ -51,11 +51,12 @@ fn integration_test(
}
}
fn search_test<P: AsRef<path::Path>>(path: P) {
fn search_test<P: AsRef<path::Path>>(path: P, args: &[&str]) {
let mut binary = env::current_dir().unwrap();
binary.push("./target/debug/just");
let output = process::Command::new(binary)
.current_dir(path)
.args(args)
.output()
.expect("just invocation failed");
@ -86,7 +87,7 @@ fn test_justfile_search() {
path.push("d");
fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory");
search_test(path);
search_test(path, &[]);
}
#[test]
@ -107,7 +108,7 @@ fn test_capitalized_justfile_search() {
path.push("d");
fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory");
search_test(path);
search_test(path, &[]);
}
#[test]
@ -139,7 +140,52 @@ fn test_capitalization_priority() {
path.push("d");
fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory");
search_test(path);
search_test(path, &[]);
}
#[test]
fn test_upwards_path_argument() {
let tmp = TempDir::new("just-test-justfile-search")
.expect("test justfile search: failed to create temporary directory");
let mut path = tmp.path().to_path_buf();
path.push("justfile");
brev::dump(&path, "default:\n\techo ok");
path.pop();
path.push("a");
fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory");
path.push("justfile");
brev::dump(&path, "default:\n\techo bad");
path.pop();
search_test(&path, &["../"]);
search_test(&path, &["../default"]);
}
#[test]
fn test_downwards_path_argument() {
let tmp = TempDir::new("just-test-justfile-search")
.expect("test justfile search: failed to create temporary directory");
let mut path = tmp.path().to_path_buf();
path.push("justfile");
brev::dump(&path, "default:\n\techo bad");
path.pop();
path.push("a");
fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory");
path.push("justfile");
brev::dump(&path, "default:\n\techo ok");
path.pop();
path.pop();
search_test(&path, &["a/"]);
search_test(&path, &["a/default"]);
search_test(&path, &["./a/"]);
search_test(&path, &["./a/default"]);
search_test(&path, &["./a/"]);
search_test(&path, &["./a/default"]);
}
#[test]