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;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
pub(crate) const DEFAULT_SHELL: &str = "sh";
|
pub(crate) const DEFAULT_SHELL: &str = "sh";
|
||||||
|
pub(crate) const INIT_JUSTFILE: &str = "default:\n\techo 'Hello, world!'\n";
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub(crate) struct Config {
|
pub(crate) struct Config {
|
||||||
@ -22,12 +23,13 @@ mod cmd {
|
|||||||
pub(crate) const DUMP: &str = "DUMP";
|
pub(crate) const DUMP: &str = "DUMP";
|
||||||
pub(crate) const EDIT: &str = "EDIT";
|
pub(crate) const EDIT: &str = "EDIT";
|
||||||
pub(crate) const EVALUATE: &str = "EVALUATE";
|
pub(crate) const EVALUATE: &str = "EVALUATE";
|
||||||
|
pub(crate) const INIT: &str = "INIT";
|
||||||
pub(crate) const LIST: &str = "LIST";
|
pub(crate) const LIST: &str = "LIST";
|
||||||
pub(crate) const SHOW: &str = "SHOW";
|
pub(crate) const SHOW: &str = "SHOW";
|
||||||
pub(crate) const SUMMARY: &str = "SUMMARY";
|
pub(crate) const SUMMARY: &str = "SUMMARY";
|
||||||
|
|
||||||
pub(crate) const ALL: &[&str] = &[DUMP, EDIT, LIST, SHOW, SUMMARY, EVALUATE];
|
pub(crate) const ALL: &[&str] = &[DUMP, EDIT, INIT, EVALUATE, LIST, SHOW, SUMMARY];
|
||||||
pub(crate) const ARGLESS: &[&str] = &[DUMP, EDIT, LIST, SHOW, SUMMARY];
|
pub(crate) const ARGLESS: &[&str] = &[DUMP, EDIT, INIT, LIST, SHOW, SUMMARY];
|
||||||
}
|
}
|
||||||
|
|
||||||
mod arg {
|
mod arg {
|
||||||
@ -70,22 +72,6 @@ impl Config {
|
|||||||
.help("Print what just would do without doing it")
|
.help("Print what just would do without doing it")
|
||||||
.conflicts_with(arg::QUIET),
|
.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(
|
||||||
Arg::with_name(arg::HIGHLIGHT)
|
Arg::with_name(arg::HIGHLIGHT)
|
||||||
.long("highlight")
|
.long("highlight")
|
||||||
@ -105,12 +91,6 @@ impl Config {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Use <JUSTFILE> as justfile."),
|
.help("Use <JUSTFILE> as justfile."),
|
||||||
)
|
)
|
||||||
.arg(
|
|
||||||
Arg::with_name(cmd::LIST)
|
|
||||||
.short("l")
|
|
||||||
.long("list")
|
|
||||||
.help("List available recipes and their arguments"),
|
|
||||||
)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(arg::QUIET)
|
Arg::with_name(arg::QUIET)
|
||||||
.short("q")
|
.short("q")
|
||||||
@ -134,19 +114,6 @@ impl Config {
|
|||||||
.default_value(DEFAULT_SHELL)
|
.default_value(DEFAULT_SHELL)
|
||||||
.help("Invoke <SHELL> to run recipes"),
|
.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(
|
||||||
Arg::with_name(arg::VERBOSE)
|
Arg::with_name(arg::VERBOSE)
|
||||||
.short("v")
|
.short("v")
|
||||||
@ -167,6 +134,46 @@ impl Config {
|
|||||||
.multiple(true)
|
.multiple(true)
|
||||||
.help("Overrides and recipe(s) to run, defaulting to the first recipe in the justfile"),
|
.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));
|
.group(ArgGroup::with_name("SUBCOMMAND").args(cmd::ALL));
|
||||||
|
|
||||||
if cfg!(feature = "help4help2man") {
|
if cfg!(feature = "help4help2man") {
|
||||||
@ -288,6 +295,8 @@ impl Config {
|
|||||||
Subcommand::Summary
|
Subcommand::Summary
|
||||||
} else if matches.is_present(cmd::DUMP) {
|
} else if matches.is_present(cmd::DUMP) {
|
||||||
Subcommand::Dump
|
Subcommand::Dump
|
||||||
|
} else if matches.is_present(cmd::INIT) {
|
||||||
|
Subcommand::Init
|
||||||
} else if matches.is_present(cmd::LIST) {
|
} else if matches.is_present(cmd::LIST) {
|
||||||
Subcommand::List
|
Subcommand::List
|
||||||
} else if let Some(name) = matches.value_of(cmd::SHOW) {
|
} else if let Some(name) = matches.value_of(cmd::SHOW) {
|
||||||
@ -295,6 +304,12 @@ impl Config {
|
|||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
}
|
}
|
||||||
} else if matches.is_present(cmd::EVALUATE) {
|
} 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 }
|
Subcommand::Evaluate { overrides }
|
||||||
} else {
|
} else {
|
||||||
Subcommand::Run {
|
Subcommand::Run {
|
||||||
@ -319,8 +334,12 @@ impl Config {
|
|||||||
pub(crate) fn run_subcommand(self) -> Result<(), i32> {
|
pub(crate) fn run_subcommand(self) -> Result<(), i32> {
|
||||||
use Subcommand::*;
|
use Subcommand::*;
|
||||||
|
|
||||||
|
if self.subcommand == Init {
|
||||||
|
return self.init();
|
||||||
|
}
|
||||||
|
|
||||||
let search =
|
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 {
|
if self.subcommand == Edit {
|
||||||
return self.edit(&search);
|
return self.edit(&search);
|
||||||
@ -355,7 +374,7 @@ impl Config {
|
|||||||
List => self.list(justfile),
|
List => self.list(justfile),
|
||||||
Show { ref name } => self.show(&name, justfile),
|
Show { ref name } => self.show(&name, justfile),
|
||||||
Summary => self.summary(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> {
|
fn list(&self, justfile: Justfile) -> Result<(), i32> {
|
||||||
// Construct a target to alias map.
|
// Construct a target to alias map.
|
||||||
let mut recipe_aliases: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
|
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`
|
Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`
|
||||||
--evaluate Print evaluated variables
|
--evaluate Print evaluated variables
|
||||||
--highlight Highlight echoed recipe lines in bold
|
--highlight Highlight echoed recipe lines in bold
|
||||||
|
--init Initialize new justfile in project root
|
||||||
-l, --list List available recipes and their arguments
|
-l, --list List available recipes and their arguments
|
||||||
--no-highlight Don't highlight echoed recipe lines in bold
|
--no-highlight Don't highlight echoed recipe lines in bold
|
||||||
-q, --quiet Suppress all output
|
-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! {
|
test! {
|
||||||
name: subcommand_list_long,
|
name: subcommand_list_long,
|
||||||
args: ["--list"],
|
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! {
|
error! {
|
||||||
name: dump_arguments,
|
name: dump_arguments,
|
||||||
args: ["--dump", "bar"],
|
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! {
|
error! {
|
||||||
name: show_arguments,
|
name: show_arguments,
|
||||||
args: ["--show", "foo", "bar"],
|
args: ["--show", "foo", "bar"],
|
||||||
@ -1157,4 +1225,9 @@ ARGS:
|
|||||||
assert_eq!(overrides, map!{"bar": "baz"});
|
assert_eq!(overrides, map!{"bar": "baz"});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_justfile() {
|
||||||
|
testing::compile(INIT_JUSTFILE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,9 @@ pub(crate) enum ConfigError {
|
|||||||
))]
|
))]
|
||||||
SearchDirConflict,
|
SearchDirConflict,
|
||||||
#[snafu(display(
|
#[snafu(display(
|
||||||
"`{}` used with unexpected arguments: {}",
|
"`{}` used with unexpected {}: {}",
|
||||||
subcommand,
|
subcommand,
|
||||||
|
Count("argument", arguments.len()),
|
||||||
List::and_ticked(arguments)
|
List::and_ticked(arguments)
|
||||||
))]
|
))]
|
||||||
SubcommandArguments {
|
SubcommandArguments {
|
||||||
|
@ -3,6 +3,7 @@ use crate::common::*;
|
|||||||
use std::path::Component;
|
use std::path::Component;
|
||||||
|
|
||||||
const FILENAME: &str = "justfile";
|
const FILENAME: &str = "justfile";
|
||||||
|
const PROJECT_ROOT_CHILDREN: &[&str] = &[".bzr", ".git", ".hg", ".svn", "_darcs"];
|
||||||
|
|
||||||
pub(crate) struct Search {
|
pub(crate) struct Search {
|
||||||
pub(crate) justfile: PathBuf,
|
pub(crate) justfile: PathBuf,
|
||||||
@ -10,7 +11,7 @@ pub(crate) struct Search {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Search {
|
impl Search {
|
||||||
pub(crate) fn search(
|
pub(crate) fn find(
|
||||||
search_config: &SearchConfig,
|
search_config: &SearchConfig,
|
||||||
invocation_directory: &Path,
|
invocation_directory: &Path,
|
||||||
) -> SearchResult<Search> {
|
) -> SearchResult<Search> {
|
||||||
@ -60,7 +61,58 @@ impl Search {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 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> {
|
fn justfile(directory: &Path) -> SearchResult<PathBuf> {
|
||||||
|
for directory in directory.ancestors() {
|
||||||
let mut candidates = Vec::new();
|
let mut candidates = Vec::new();
|
||||||
|
|
||||||
let entries = fs::read_dir(directory).map_err(|io_error| SearchError::Io {
|
let entries = fs::read_dir(directory).map_err(|io_error| SearchError::Io {
|
||||||
@ -79,16 +131,15 @@ impl Search {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if candidates.len() == 1 {
|
if candidates.len() == 1 {
|
||||||
Ok(candidates.pop().unwrap())
|
return Ok(candidates.pop().unwrap());
|
||||||
} else if candidates.len() > 1 {
|
} else if candidates.len() > 1 {
|
||||||
Err(SearchError::MultipleCandidates { candidates })
|
return 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 {
|
fn clean(invocation_directory: &Path, path: &Path) -> PathBuf {
|
||||||
let path = invocation_directory.join(path);
|
let path = invocation_directory.join(path);
|
||||||
|
|
||||||
@ -107,6 +158,29 @@ impl Search {
|
|||||||
clean.into_iter().collect()
|
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> {
|
fn working_directory_from_justfile(justfile: &Path) -> SearchResult<PathBuf> {
|
||||||
Ok(
|
Ok(
|
||||||
justfile
|
justfile
|
||||||
@ -260,7 +334,7 @@ mod tests {
|
|||||||
|
|
||||||
let search_config = SearchConfig::FromInvocationDirectory;
|
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.justfile, justfile);
|
||||||
assert_eq!(search.working_directory, sub);
|
assert_eq!(search.working_directory, sub);
|
||||||
|
@ -7,11 +7,12 @@ pub(crate) enum Subcommand {
|
|||||||
Evaluate {
|
Evaluate {
|
||||||
overrides: BTreeMap<String, String>,
|
overrides: BTreeMap<String, String>,
|
||||||
},
|
},
|
||||||
|
Init,
|
||||||
|
List,
|
||||||
Run {
|
Run {
|
||||||
overrides: BTreeMap<String, String>,
|
overrides: BTreeMap<String, String>,
|
||||||
arguments: Vec<String>,
|
arguments: Vec<String>,
|
||||||
},
|
},
|
||||||
List,
|
|
||||||
Show {
|
Show {
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
@ -134,13 +134,13 @@ macro_rules! entries {
|
|||||||
std::collections::HashMap::new()
|
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();
|
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
|
entries
|
||||||
@ -148,6 +148,20 @@ macro_rules! entries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! name {
|
||||||
|
{
|
||||||
|
$name:ident
|
||||||
|
} => {
|
||||||
|
stringify!($name)
|
||||||
|
};
|
||||||
|
{
|
||||||
|
$name:literal
|
||||||
|
} => {
|
||||||
|
$name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! tmptree {
|
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