mirror of
https://github.com/casey/just.git
synced 2024-11-23 11:04:09 +03:00
Add --init
subcommand (#541)
When `--init` is passed on the command line, search upward for the project root, identified by the presence of a VCS directory like `.git`, falling back to the current directory, and create a default justfile in that directory.
This commit is contained in:
parent
c4e9857ebd
commit
e948f11784
151
src/config.rs
151
src/config.rs
@ -4,6 +4,7 @@ use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
pub(crate) const DEFAULT_SHELL: &str = "sh";
|
||||
pub(crate) const INIT_JUSTFILE: &str = "default:\n\techo 'Hello, world!'\n";
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct Config {
|
||||
@ -22,12 +23,13 @@ mod cmd {
|
||||
pub(crate) const DUMP: &str = "DUMP";
|
||||
pub(crate) const EDIT: &str = "EDIT";
|
||||
pub(crate) const EVALUATE: &str = "EVALUATE";
|
||||
pub(crate) const INIT: &str = "INIT";
|
||||
pub(crate) const LIST: &str = "LIST";
|
||||
pub(crate) const SHOW: &str = "SHOW";
|
||||
pub(crate) const SUMMARY: &str = "SUMMARY";
|
||||
|
||||
pub(crate) const ALL: &[&str] = &[DUMP, EDIT, LIST, SHOW, SUMMARY, EVALUATE];
|
||||
pub(crate) const ARGLESS: &[&str] = &[DUMP, EDIT, LIST, SHOW, SUMMARY];
|
||||
pub(crate) const ALL: &[&str] = &[DUMP, EDIT, INIT, EVALUATE, LIST, SHOW, SUMMARY];
|
||||
pub(crate) const ARGLESS: &[&str] = &[DUMP, EDIT, INIT, LIST, SHOW, SUMMARY];
|
||||
}
|
||||
|
||||
mod arg {
|
||||
@ -70,22 +72,6 @@ impl Config {
|
||||
.help("Print what just would do without doing it")
|
||||
.conflicts_with(arg::QUIET),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::DUMP)
|
||||
.long("dump")
|
||||
.help("Print entire justfile"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::EDIT)
|
||||
.short("e")
|
||||
.long("edit")
|
||||
.help("Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::EVALUATE)
|
||||
.long("evaluate")
|
||||
.help("Print evaluated variables"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(arg::HIGHLIGHT)
|
||||
.long("highlight")
|
||||
@ -105,12 +91,6 @@ impl Config {
|
||||
.takes_value(true)
|
||||
.help("Use <JUSTFILE> as justfile."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::LIST)
|
||||
.short("l")
|
||||
.long("list")
|
||||
.help("List available recipes and their arguments"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(arg::QUIET)
|
||||
.short("q")
|
||||
@ -134,19 +114,6 @@ impl Config {
|
||||
.default_value(DEFAULT_SHELL)
|
||||
.help("Invoke <SHELL> to run recipes"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::SHOW)
|
||||
.short("s")
|
||||
.long("show")
|
||||
.takes_value(true)
|
||||
.value_name("RECIPE")
|
||||
.help("Show information about <RECIPE>"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::SUMMARY)
|
||||
.long("summary")
|
||||
.help("List names of available recipes"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(arg::VERBOSE)
|
||||
.short("v")
|
||||
@ -167,6 +134,46 @@ impl Config {
|
||||
.multiple(true)
|
||||
.help("Overrides and recipe(s) to run, defaulting to the first recipe in the justfile"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::DUMP)
|
||||
.long("dump")
|
||||
.help("Print entire justfile"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::EDIT)
|
||||
.short("e")
|
||||
.long("edit")
|
||||
.help("Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::EVALUATE)
|
||||
.long("evaluate")
|
||||
.help("Print evaluated variables"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::INIT)
|
||||
.long("init")
|
||||
.help("Initialize new justfile in project root"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::LIST)
|
||||
.short("l")
|
||||
.long("list")
|
||||
.help("List available recipes and their arguments"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::SHOW)
|
||||
.short("s")
|
||||
.long("show")
|
||||
.takes_value(true)
|
||||
.value_name("RECIPE")
|
||||
.help("Show information about <RECIPE>"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(cmd::SUMMARY)
|
||||
.long("summary")
|
||||
.help("List names of available recipes"),
|
||||
)
|
||||
.group(ArgGroup::with_name("SUBCOMMAND").args(cmd::ALL));
|
||||
|
||||
if cfg!(feature = "help4help2man") {
|
||||
@ -288,6 +295,8 @@ impl Config {
|
||||
Subcommand::Summary
|
||||
} else if matches.is_present(cmd::DUMP) {
|
||||
Subcommand::Dump
|
||||
} else if matches.is_present(cmd::INIT) {
|
||||
Subcommand::Init
|
||||
} else if matches.is_present(cmd::LIST) {
|
||||
Subcommand::List
|
||||
} else if let Some(name) = matches.value_of(cmd::SHOW) {
|
||||
@ -295,6 +304,12 @@ impl Config {
|
||||
name: name.to_owned(),
|
||||
}
|
||||
} else if matches.is_present(cmd::EVALUATE) {
|
||||
if !positional.arguments.is_empty() {
|
||||
return Err(ConfigError::SubcommandArguments {
|
||||
subcommand: format!("--{}", cmd::EVALUATE.to_lowercase()),
|
||||
arguments: positional.arguments,
|
||||
});
|
||||
}
|
||||
Subcommand::Evaluate { overrides }
|
||||
} else {
|
||||
Subcommand::Run {
|
||||
@ -319,8 +334,12 @@ impl Config {
|
||||
pub(crate) fn run_subcommand(self) -> Result<(), i32> {
|
||||
use Subcommand::*;
|
||||
|
||||
if self.subcommand == Init {
|
||||
return self.init();
|
||||
}
|
||||
|
||||
let search =
|
||||
Search::search(&self.search_config, &self.invocation_directory).eprint(self.color)?;
|
||||
Search::find(&self.search_config, &self.invocation_directory).eprint(self.color)?;
|
||||
|
||||
if self.subcommand == Edit {
|
||||
return self.edit(&search);
|
||||
@ -355,7 +374,7 @@ impl Config {
|
||||
List => self.list(justfile),
|
||||
Show { ref name } => self.show(&name, justfile),
|
||||
Summary => self.summary(justfile),
|
||||
Edit => unreachable!(),
|
||||
Edit | Init => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,6 +413,26 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn init(&self) -> Result<(), i32> {
|
||||
let search =
|
||||
Search::init(&self.search_config, &self.invocation_directory).eprint(self.color)?;
|
||||
|
||||
if search.justfile.exists() {
|
||||
eprintln!("Justfile `{}` already exists", search.justfile.display());
|
||||
Err(EXIT_FAILURE)
|
||||
} else if let Err(err) = fs::write(&search.justfile, INIT_JUSTFILE) {
|
||||
eprintln!(
|
||||
"Failed to write justfile to `{}`: {}",
|
||||
search.justfile.display(),
|
||||
err
|
||||
);
|
||||
Err(EXIT_FAILURE)
|
||||
} else {
|
||||
eprintln!("Wrote justfile to `{}`", search.justfile.display());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn list(&self, justfile: Justfile) -> Result<(), i32> {
|
||||
// Construct a target to alias map.
|
||||
let mut recipe_aliases: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
|
||||
@ -561,6 +600,7 @@ FLAGS:
|
||||
Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`
|
||||
--evaluate Print evaluated variables
|
||||
--highlight Highlight echoed recipe lines in bold
|
||||
--init Initialize new justfile in project root
|
||||
-l, --list List available recipes and their arguments
|
||||
--no-highlight Don't highlight echoed recipe lines in bold
|
||||
-q, --quiet Suppress all output
|
||||
@ -922,6 +962,14 @@ ARGS:
|
||||
},
|
||||
}
|
||||
|
||||
test! {
|
||||
name: subcommand_evaluate_overrides,
|
||||
args: ["--evaluate", "x=y"],
|
||||
subcommand: Subcommand::Evaluate {
|
||||
overrides: map!{"x": "y"},
|
||||
},
|
||||
}
|
||||
|
||||
test! {
|
||||
name: subcommand_list_long,
|
||||
args: ["--list"],
|
||||
@ -1097,6 +1145,16 @@ ARGS:
|
||||
},
|
||||
}
|
||||
|
||||
error! {
|
||||
name: evaluate_arguments,
|
||||
args: ["--evaluate", "bar"],
|
||||
error: ConfigError::SubcommandArguments { subcommand, arguments },
|
||||
check: {
|
||||
assert_eq!(subcommand, "--evaluate");
|
||||
assert_eq!(arguments, &["bar"]);
|
||||
},
|
||||
}
|
||||
|
||||
error! {
|
||||
name: dump_arguments,
|
||||
args: ["--dump", "bar"],
|
||||
@ -1117,6 +1175,16 @@ ARGS:
|
||||
},
|
||||
}
|
||||
|
||||
error! {
|
||||
name: init_arguments,
|
||||
args: ["--init", "bar"],
|
||||
error: ConfigError::SubcommandArguments { subcommand, arguments },
|
||||
check: {
|
||||
assert_eq!(subcommand, "--init");
|
||||
assert_eq!(arguments, &["bar"]);
|
||||
},
|
||||
}
|
||||
|
||||
error! {
|
||||
name: show_arguments,
|
||||
args: ["--show", "foo", "bar"],
|
||||
@ -1157,4 +1225,9 @@ ARGS:
|
||||
assert_eq!(overrides, map!{"bar": "baz"});
|
||||
},
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_justfile() {
|
||||
testing::compile(INIT_JUSTFILE);
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,9 @@ pub(crate) enum ConfigError {
|
||||
))]
|
||||
SearchDirConflict,
|
||||
#[snafu(display(
|
||||
"`{}` used with unexpected arguments: {}",
|
||||
"`{}` used with unexpected {}: {}",
|
||||
subcommand,
|
||||
Count("argument", arguments.len()),
|
||||
List::and_ticked(arguments)
|
||||
))]
|
||||
SubcommandArguments {
|
||||
|
118
src/search.rs
118
src/search.rs
@ -3,6 +3,7 @@ use crate::common::*;
|
||||
use std::path::Component;
|
||||
|
||||
const FILENAME: &str = "justfile";
|
||||
const PROJECT_ROOT_CHILDREN: &[&str] = &[".bzr", ".git", ".hg", ".svn", "_darcs"];
|
||||
|
||||
pub(crate) struct Search {
|
||||
pub(crate) justfile: PathBuf,
|
||||
@ -10,7 +11,7 @@ pub(crate) struct Search {
|
||||
}
|
||||
|
||||
impl Search {
|
||||
pub(crate) fn search(
|
||||
pub(crate) fn find(
|
||||
search_config: &SearchConfig,
|
||||
invocation_directory: &Path,
|
||||
) -> SearchResult<Search> {
|
||||
@ -60,33 +61,83 @@ impl Search {
|
||||
}
|
||||
}
|
||||
|
||||
fn justfile(directory: &Path) -> SearchResult<PathBuf> {
|
||||
let mut candidates = Vec::new();
|
||||
pub(crate) fn init(
|
||||
search_config: &SearchConfig,
|
||||
invocation_directory: &Path,
|
||||
) -> SearchResult<Search> {
|
||||
match search_config {
|
||||
SearchConfig::FromInvocationDirectory => {
|
||||
let working_directory = Self::project_root(&invocation_directory)?;
|
||||
|
||||
let entries = fs::read_dir(directory).map_err(|io_error| SearchError::Io {
|
||||
io_error,
|
||||
directory: directory.to_owned(),
|
||||
})?;
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|io_error| SearchError::Io {
|
||||
let justfile = working_directory.join(FILENAME);
|
||||
|
||||
Ok(Search {
|
||||
justfile,
|
||||
working_directory,
|
||||
})
|
||||
}
|
||||
|
||||
SearchConfig::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(FILENAME);
|
||||
|
||||
Ok(Search {
|
||||
justfile,
|
||||
working_directory,
|
||||
})
|
||||
}
|
||||
|
||||
SearchConfig::WithJustfile { justfile } => {
|
||||
let justfile = Self::clean(invocation_directory, justfile);
|
||||
|
||||
let working_directory = Self::working_directory_from_justfile(&justfile)?;
|
||||
|
||||
Ok(Search {
|
||||
justfile,
|
||||
working_directory,
|
||||
})
|
||||
}
|
||||
|
||||
SearchConfig::WithJustfileAndWorkingDirectory {
|
||||
justfile,
|
||||
working_directory,
|
||||
} => Ok(Search {
|
||||
justfile: Self::clean(invocation_directory, justfile),
|
||||
working_directory: Self::clean(invocation_directory, working_directory),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn justfile(directory: &Path) -> SearchResult<PathBuf> {
|
||||
for directory in directory.ancestors() {
|
||||
let mut candidates = Vec::new();
|
||||
|
||||
let entries = fs::read_dir(directory).map_err(|io_error| SearchError::Io {
|
||||
io_error,
|
||||
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 entry in entries {
|
||||
let entry = entry.map_err(|io_error| SearchError::Io {
|
||||
io_error,
|
||||
directory: directory.to_owned(),
|
||||
})?;
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
if name.eq_ignore_ascii_case(FILENAME) {
|
||||
candidates.push(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
if candidates.len() == 1 {
|
||||
return Ok(candidates.pop().unwrap());
|
||||
} else if candidates.len() > 1 {
|
||||
return Err(SearchError::MultipleCandidates { candidates });
|
||||
}
|
||||
}
|
||||
if candidates.len() == 1 {
|
||||
Ok(candidates.pop().unwrap())
|
||||
} else if candidates.len() > 1 {
|
||||
Err(SearchError::MultipleCandidates { candidates })
|
||||
} else if let Some(parent) = directory.parent() {
|
||||
Self::justfile(parent)
|
||||
} else {
|
||||
Err(SearchError::NotFound)
|
||||
}
|
||||
|
||||
Err(SearchError::NotFound)
|
||||
}
|
||||
|
||||
fn clean(invocation_directory: &Path, path: &Path) -> PathBuf {
|
||||
@ -107,6 +158,29 @@ impl Search {
|
||||
clean.into_iter().collect()
|
||||
}
|
||||
|
||||
fn project_root(directory: &Path) -> SearchResult<PathBuf> {
|
||||
for directory in directory.ancestors() {
|
||||
let entries = fs::read_dir(directory).map_err(|io_error| SearchError::Io {
|
||||
io_error,
|
||||
directory: directory.to_owned(),
|
||||
})?;
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|io_error| SearchError::Io {
|
||||
io_error,
|
||||
directory: directory.to_owned(),
|
||||
})?;
|
||||
for project_root_child in PROJECT_ROOT_CHILDREN.iter().cloned() {
|
||||
if entry.file_name() == project_root_child {
|
||||
return Ok(directory.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(directory.to_owned())
|
||||
}
|
||||
|
||||
fn working_directory_from_justfile(justfile: &Path) -> SearchResult<PathBuf> {
|
||||
Ok(
|
||||
justfile
|
||||
@ -260,7 +334,7 @@ mod tests {
|
||||
|
||||
let search_config = SearchConfig::FromInvocationDirectory;
|
||||
|
||||
let search = Search::search(&search_config, &sub).unwrap();
|
||||
let search = Search::find(&search_config, &sub).unwrap();
|
||||
|
||||
assert_eq!(search.justfile, justfile);
|
||||
assert_eq!(search.working_directory, sub);
|
||||
|
@ -7,11 +7,12 @@ pub(crate) enum Subcommand {
|
||||
Evaluate {
|
||||
overrides: BTreeMap<String, String>,
|
||||
},
|
||||
Init,
|
||||
List,
|
||||
Run {
|
||||
overrides: BTreeMap<String, String>,
|
||||
arguments: Vec<String>,
|
||||
},
|
||||
List,
|
||||
Show {
|
||||
name: String,
|
||||
},
|
||||
|
@ -134,13 +134,13 @@ macro_rules! entries {
|
||||
std::collections::HashMap::new()
|
||||
};
|
||||
{
|
||||
$($name:ident : $contents:tt,)*
|
||||
$($name:tt : $contents:tt,)*
|
||||
} => {
|
||||
{
|
||||
let mut entries: std::collections::HashMap<&'static str, $crate::Entry> = std::collections::HashMap::new();
|
||||
|
||||
$(
|
||||
entries.insert(stringify!($name), $crate::entry!($contents));
|
||||
entries.insert($crate::name!($name), $crate::entry!($contents));
|
||||
)*
|
||||
|
||||
entries
|
||||
@ -148,6 +148,20 @@ macro_rules! entries {
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! name {
|
||||
{
|
||||
$name:ident
|
||||
} => {
|
||||
stringify!($name)
|
||||
};
|
||||
{
|
||||
$name:literal
|
||||
} => {
|
||||
$name
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! tmptree {
|
||||
{
|
||||
|
159
tests/init.rs
Normal file
159
tests/init.rs
Normal file
@ -0,0 +1,159 @@
|
||||
use std::{fs, process::Command};
|
||||
|
||||
use executable_path::executable_path;
|
||||
|
||||
use test_utilities::{tempdir, tmptree};
|
||||
|
||||
const EXPECTED: &str = "default:\n\techo 'Hello, world!'\n";
|
||||
|
||||
#[test]
|
||||
fn current_dir() {
|
||||
let tmp = tempdir();
|
||||
|
||||
let output = Command::new(executable_path("just"))
|
||||
.current_dir(tmp.path())
|
||||
.arg("--init")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("justfile")).unwrap(),
|
||||
EXPECTED
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exists() {
|
||||
let tmp = tempdir();
|
||||
|
||||
let output = Command::new(executable_path("just"))
|
||||
.current_dir(tmp.path())
|
||||
.arg("--init")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
|
||||
let output = Command::new(executable_path("just"))
|
||||
.current_dir(tmp.path())
|
||||
.arg("--init")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(!output.status.success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invocation_directory() {
|
||||
let tmp = tmptree! {
|
||||
".git": {},
|
||||
};
|
||||
|
||||
let output = Command::new(executable_path("just"))
|
||||
.current_dir(tmp.path())
|
||||
.arg("--init")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("justfile")).unwrap(),
|
||||
EXPECTED
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alternate_marker() {
|
||||
let tmp = tmptree! {
|
||||
"_darcs": {},
|
||||
};
|
||||
|
||||
let output = Command::new(executable_path("just"))
|
||||
.current_dir(tmp.path())
|
||||
.arg("--init")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("justfile")).unwrap(),
|
||||
EXPECTED
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_directory() {
|
||||
let tmp = tmptree! {
|
||||
sub: {
|
||||
".git": {},
|
||||
},
|
||||
};
|
||||
|
||||
let output = Command::new(executable_path("just"))
|
||||
.current_dir(tmp.path())
|
||||
.arg("--init")
|
||||
.arg("sub/")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("sub/justfile")).unwrap(),
|
||||
EXPECTED
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justfile() {
|
||||
let tmp = tmptree! {
|
||||
sub: {
|
||||
".git": {},
|
||||
},
|
||||
};
|
||||
|
||||
let output = Command::new(executable_path("just"))
|
||||
.current_dir(tmp.path().join("sub"))
|
||||
.arg("--init")
|
||||
.arg("--justfile")
|
||||
.arg(tmp.path().join("justfile"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("justfile")).unwrap(),
|
||||
EXPECTED
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justfile_and_working_directory() {
|
||||
let tmp = tmptree! {
|
||||
sub: {
|
||||
".git": {},
|
||||
},
|
||||
};
|
||||
|
||||
let output = Command::new(executable_path("just"))
|
||||
.current_dir(tmp.path().join("sub"))
|
||||
.arg("--init")
|
||||
.arg("--justfile")
|
||||
.arg(tmp.path().join("justfile"))
|
||||
.arg("--working-directory")
|
||||
.arg("/")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("justfile")).unwrap(),
|
||||
EXPECTED
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user