diff --git a/README.md b/README.md index 1bc26bc..68b193f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/app.rs b/src/app.rs index 62e19ed..67c6376 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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::>()) + .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::>(); + 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::>()) - .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::>(); - let arguments = if !rest.is_empty() { rest } else if let Some(recipe) = justfile.first() { diff --git a/src/integration.rs b/src/integration.rs index 14f8a61..d26cd13 100644 --- a/src/integration.rs +++ b/src/integration.rs @@ -51,11 +51,12 @@ fn integration_test( } } -fn search_test>(path: P) { +fn search_test>(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]