2019-04-11 15:23:14 -07:00
|
|
|
use crate::common::*;
|
2016-10-23 16:43:52 -07:00
|
|
|
|
2019-10-07 02:06:45 -07:00
|
|
|
use crate::{interrupt_handler::InterruptHandler, misc::maybe_s};
|
2018-12-08 14:29:41 -08:00
|
|
|
use std::{convert, ffi};
|
|
|
|
use unicode_width::UnicodeWidthStr;
|
2016-10-23 16:43:52 -07:00
|
|
|
|
2018-02-16 00:52:00 -08:00
|
|
|
#[cfg(windows)]
|
|
|
|
use ansi_term::enable_ansi_support;
|
|
|
|
|
2016-11-12 14:02:15 -08:00
|
|
|
fn edit<P: convert::AsRef<ffi::OsStr>>(path: P) -> ! {
|
2018-12-08 14:29:41 -08:00
|
|
|
let editor =
|
|
|
|
env::var_os("EDITOR").unwrap_or_else(|| die!("Error getting EDITOR environment variable"));
|
2016-11-12 14:02:15 -08:00
|
|
|
|
2018-12-08 14:29:41 -08:00
|
|
|
let error = Command::new(editor).arg(path).status();
|
2016-11-12 14:02:15 -08:00
|
|
|
|
|
|
|
match error {
|
2017-04-21 22:11:18 -07:00
|
|
|
Ok(status) => process::exit(status.code().unwrap_or(EXIT_FAILURE)),
|
2016-11-12 14:02:15 -08:00
|
|
|
Err(error) => die!("Failed to invoke editor: {}", error),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-16 23:30:08 -08:00
|
|
|
pub fn run() {
|
2018-05-06 19:02:17 -07:00
|
|
|
#[cfg(windows)]
|
|
|
|
enable_ansi_support().ok();
|
|
|
|
|
2018-08-27 16:03:52 -07:00
|
|
|
env_logger::Builder::from_env(
|
2018-12-08 14:29:41 -08:00
|
|
|
env_logger::Env::new()
|
|
|
|
.filter("JUST_LOG")
|
|
|
|
.write_style("JUST_LOG_STYLE"),
|
|
|
|
)
|
|
|
|
.init();
|
2018-08-27 16:03:52 -07:00
|
|
|
|
2018-12-08 14:29:41 -08:00
|
|
|
let invocation_directory =
|
|
|
|
env::current_dir().map_err(|e| format!("Error getting current directory: {}", e));
|
2018-06-19 10:04:03 -07:00
|
|
|
|
2019-10-07 02:06:45 -07:00
|
|
|
let app = Config::app();
|
2019-07-18 21:58:06 -07:00
|
|
|
|
|
|
|
let matches = app.get_matches();
|
2016-10-23 16:43:52 -07:00
|
|
|
|
2019-10-07 02:06:45 -07:00
|
|
|
let config = Config::from_matches(&matches);
|
2016-12-30 00:09:35 -08:00
|
|
|
|
2019-04-08 00:54:05 -07:00
|
|
|
let justfile = matches.value_of("JUSTFILE").map(Path::new);
|
2019-10-07 02:06:45 -07:00
|
|
|
|
2019-04-08 00:54:05 -07:00
|
|
|
let mut working_directory = matches.value_of("WORKING-DIRECTORY").map(PathBuf::from);
|
|
|
|
|
|
|
|
if let (Some(justfile), None) = (justfile, working_directory.as_ref()) {
|
|
|
|
let mut justfile = justfile.to_path_buf();
|
|
|
|
|
|
|
|
if !justfile.is_absolute() {
|
|
|
|
match justfile.canonicalize() {
|
|
|
|
Ok(canonical) => justfile = canonical,
|
|
|
|
Err(err) => die!(
|
|
|
|
"Could not canonicalize justfile path `{}`: {}",
|
|
|
|
justfile.display(),
|
|
|
|
err
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
justfile.pop();
|
|
|
|
|
|
|
|
working_directory = Some(justfile);
|
|
|
|
}
|
2016-10-23 19:56:30 -07:00
|
|
|
|
|
|
|
let text;
|
2019-04-08 00:54:05 -07:00
|
|
|
if let (Some(justfile), Some(directory)) = (justfile, working_directory) {
|
2019-10-07 04:04:39 -07:00
|
|
|
if config.subcommand == Subcommand::Edit {
|
2019-04-08 00:54:05 -07:00
|
|
|
edit(justfile);
|
2016-11-12 14:02:15 -08:00
|
|
|
}
|
|
|
|
|
2019-04-16 22:06:28 -07:00
|
|
|
text = fs::read_to_string(justfile)
|
2016-10-23 19:56:30 -07:00
|
|
|
.unwrap_or_else(|error| die!("Error reading justfile: {}", error));
|
|
|
|
|
2019-04-08 00:54:05 -07:00
|
|
|
if let Err(error) = env::set_current_dir(&directory) {
|
|
|
|
die!(
|
|
|
|
"Error changing directory to {}: {}",
|
|
|
|
directory.display(),
|
|
|
|
error
|
|
|
|
);
|
2016-10-23 19:56:30 -07:00
|
|
|
}
|
|
|
|
} else {
|
2019-06-01 22:38:03 -07:00
|
|
|
let current_dir = match env::current_dir() {
|
|
|
|
Ok(current_dir) => current_dir,
|
|
|
|
Err(io_error) => die!("Error getting current dir: {}", io_error),
|
|
|
|
};
|
|
|
|
match search::justfile(¤t_dir) {
|
|
|
|
Ok(name) => {
|
2019-10-07 04:04:39 -07:00
|
|
|
if config.subcommand == Subcommand::Edit {
|
2019-06-01 22:38:03 -07:00
|
|
|
edit(name);
|
2016-10-23 16:43:52 -07:00
|
|
|
}
|
2019-06-01 22:38:03 -07:00
|
|
|
text = fs::read_to_string(&name)
|
|
|
|
.unwrap_or_else(|error| die!("Error reading justfile: {}", error));
|
2016-10-23 16:43:52 -07:00
|
|
|
|
2019-06-01 22:38:03 -07:00
|
|
|
let parent = name.parent().unwrap();
|
2016-10-23 16:43:52 -07:00
|
|
|
|
2019-06-01 22:38:03 -07:00
|
|
|
if let Err(error) = env::set_current_dir(&parent) {
|
|
|
|
die!(
|
|
|
|
"Error changing directory to {}: {}",
|
|
|
|
parent.display(),
|
|
|
|
error
|
|
|
|
);
|
|
|
|
}
|
2016-10-23 19:56:30 -07:00
|
|
|
}
|
2019-06-01 22:38:03 -07:00
|
|
|
Err(search_error) => die!("{}", search_error),
|
2016-10-23 16:43:52 -07:00
|
|
|
}
|
2016-10-23 19:56:30 -07:00
|
|
|
}
|
2016-10-23 16:43:52 -07:00
|
|
|
|
2018-12-08 14:29:41 -08:00
|
|
|
let justfile = Parser::parse(&text).unwrap_or_else(|error| {
|
2019-10-07 02:06:45 -07:00
|
|
|
if config.color.stderr().active() {
|
2016-11-07 21:01:27 -08:00
|
|
|
die!("{:#}", error);
|
|
|
|
} else {
|
|
|
|
die!("{}", error);
|
|
|
|
}
|
2018-12-08 14:29:41 -08:00
|
|
|
});
|
2016-10-23 16:43:52 -07:00
|
|
|
|
2019-09-21 18:53:30 -07:00
|
|
|
for warning in &justfile.warnings {
|
2019-10-07 02:06:45 -07:00
|
|
|
if config.color.stderr().active() {
|
2019-09-21 18:53:30 -07:00
|
|
|
eprintln!("{:#}", warning);
|
|
|
|
} else {
|
|
|
|
eprintln!("{}", warning);
|
|
|
|
}
|
2019-04-18 11:48:02 -07:00
|
|
|
}
|
|
|
|
|
2019-10-07 04:04:39 -07:00
|
|
|
if config.subcommand == Subcommand::Summary {
|
2016-10-23 16:43:52 -07:00
|
|
|
if justfile.count() == 0 {
|
2017-08-18 14:15:43 -07:00
|
|
|
eprintln!("Justfile contains no recipes.");
|
2016-10-23 16:43:52 -07:00
|
|
|
} else {
|
2018-12-08 14:29:41 -08:00
|
|
|
let summary = justfile
|
|
|
|
.recipes
|
|
|
|
.iter()
|
2017-10-06 23:48:07 -07:00
|
|
|
.filter(|&(_, recipe)| !recipe.private)
|
|
|
|
.map(|(name, _)| name)
|
|
|
|
.cloned()
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(" ");
|
|
|
|
println!("{}", summary);
|
2016-10-23 16:43:52 -07:00
|
|
|
}
|
2017-04-21 22:11:18 -07:00
|
|
|
process::exit(EXIT_SUCCESS);
|
2016-10-23 16:43:52 -07:00
|
|
|
}
|
|
|
|
|
2019-10-07 04:04:39 -07:00
|
|
|
if config.subcommand == Subcommand::Dump {
|
2016-11-11 20:25:37 -08:00
|
|
|
println!("{}", justfile);
|
2017-04-21 22:11:18 -07:00
|
|
|
process::exit(EXIT_SUCCESS);
|
2016-11-11 20:25:37 -08:00
|
|
|
}
|
|
|
|
|
2019-10-07 04:04:39 -07:00
|
|
|
if config.subcommand == Subcommand::List {
|
2019-04-11 12:30:29 -07:00
|
|
|
// Construct a target to alias map.
|
2019-04-11 15:23:14 -07:00
|
|
|
let mut recipe_aliases: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
|
2019-04-11 12:30:29 -07:00
|
|
|
for alias in justfile.aliases.values() {
|
2019-04-11 15:57:34 -07:00
|
|
|
if alias.private {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-04-11 12:30:29 -07:00
|
|
|
if !recipe_aliases.contains_key(alias.target) {
|
|
|
|
recipe_aliases.insert(alias.target, vec![alias.name]);
|
|
|
|
} else {
|
|
|
|
let aliases = recipe_aliases.get_mut(alias.target).unwrap();
|
|
|
|
aliases.push(alias.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-11 15:23:14 -07:00
|
|
|
let mut line_widths: BTreeMap<&str, usize> = BTreeMap::new();
|
2017-11-30 15:03:59 -08:00
|
|
|
|
|
|
|
for (name, recipe) in &justfile.recipes {
|
|
|
|
if recipe.private {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-04-11 12:30:29 -07:00
|
|
|
for name in iter::once(name).chain(recipe_aliases.get(name).unwrap_or(&Vec::new())) {
|
|
|
|
let mut line_width = UnicodeWidthStr::width(*name);
|
2017-11-30 15:03:59 -08:00
|
|
|
|
2019-04-11 12:30:29 -07:00
|
|
|
for parameter in &recipe.parameters {
|
|
|
|
line_width += UnicodeWidthStr::width(format!(" {}", parameter).as_str());
|
|
|
|
}
|
2017-11-30 15:03:59 -08:00
|
|
|
|
2019-04-11 12:30:29 -07:00
|
|
|
if line_width <= 30 {
|
|
|
|
line_widths.insert(name, line_width);
|
|
|
|
}
|
2017-11-30 15:03:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let max_line_width = cmp::min(line_widths.values().cloned().max().unwrap_or(0), 30);
|
|
|
|
|
2019-10-07 02:06:45 -07:00
|
|
|
let doc_color = config.color.stdout().doc();
|
2016-11-12 11:40:52 -08:00
|
|
|
println!("Available recipes:");
|
2019-04-11 12:30:29 -07:00
|
|
|
|
2016-11-12 11:40:52 -08:00
|
|
|
for (name, recipe) in &justfile.recipes {
|
2017-10-06 23:48:07 -07:00
|
|
|
if recipe.private {
|
|
|
|
continue;
|
|
|
|
}
|
2019-04-11 12:30:29 -07:00
|
|
|
|
|
|
|
let alias_doc = format!("alias for `{}`", recipe.name);
|
|
|
|
|
2019-04-11 15:23:14 -07:00
|
|
|
for (i, name) in iter::once(name)
|
|
|
|
.chain(recipe_aliases.get(name).unwrap_or(&Vec::new()))
|
|
|
|
.enumerate()
|
|
|
|
{
|
2019-04-11 12:30:29 -07:00
|
|
|
print!(" {}", name);
|
|
|
|
for parameter in &recipe.parameters {
|
2019-10-07 02:06:45 -07:00
|
|
|
if config.color.stdout().active() {
|
2019-04-11 12:30:29 -07:00
|
|
|
print!(" {:#}", parameter);
|
|
|
|
} else {
|
|
|
|
print!(" {}", parameter);
|
|
|
|
}
|
2016-11-12 23:31:19 -08:00
|
|
|
}
|
2019-04-11 12:30:29 -07:00
|
|
|
|
2019-04-11 15:23:14 -07:00
|
|
|
// Declaring this outside of the nested loops will probably be more efficient, but
|
2019-04-11 12:30:29 -07:00
|
|
|
// it creates all sorts of lifetime issues with variables inside the loops.
|
|
|
|
// If this is inlined like the docs say, it shouldn't make any difference.
|
|
|
|
let print_doc = |doc| {
|
|
|
|
print!(
|
|
|
|
" {:padding$}{} {}",
|
|
|
|
"",
|
|
|
|
doc_color.paint("#"),
|
|
|
|
doc_color.paint(doc),
|
2019-04-11 15:23:14 -07:00
|
|
|
padding = max_line_width
|
|
|
|
.saturating_sub(line_widths.get(name).cloned().unwrap_or(max_line_width))
|
2019-04-11 12:30:29 -07:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
match (i, recipe.doc) {
|
|
|
|
(0, Some(doc)) => print_doc(doc),
|
2019-04-11 15:23:14 -07:00
|
|
|
(0, None) => (),
|
|
|
|
_ => print_doc(&alias_doc),
|
2019-04-11 12:30:29 -07:00
|
|
|
}
|
|
|
|
println!();
|
2016-11-12 11:40:52 -08:00
|
|
|
}
|
|
|
|
}
|
2019-04-11 12:30:29 -07:00
|
|
|
|
2017-04-21 22:11:18 -07:00
|
|
|
process::exit(EXIT_SUCCESS);
|
2016-11-12 11:40:52 -08:00
|
|
|
}
|
|
|
|
|
2019-10-07 04:04:39 -07:00
|
|
|
if let Subcommand::Show { name } = config.subcommand {
|
2019-08-23 20:45:57 -07:00
|
|
|
if let Some(alias) = justfile.get_alias(name) {
|
|
|
|
let recipe = justfile.get_recipe(alias.target).unwrap();
|
|
|
|
println!("{}", alias);
|
|
|
|
println!("{}", recipe);
|
|
|
|
process::exit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
if let Some(recipe) = justfile.get_recipe(name) {
|
|
|
|
println!("{}", recipe);
|
|
|
|
process::exit(EXIT_SUCCESS);
|
|
|
|
} else {
|
|
|
|
eprintln!("Justfile does not contain recipe `{}`.", name);
|
|
|
|
if let Some(suggestion) = justfile.suggest(name) {
|
|
|
|
eprintln!("Did you mean `{}`?", suggestion);
|
2016-11-12 12:36:12 -08:00
|
|
|
}
|
2019-08-23 20:45:57 -07:00
|
|
|
process::exit(EXIT_FAILURE)
|
2016-10-23 16:43:52 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-07 02:06:45 -07:00
|
|
|
let arguments = if !config.arguments.is_empty() {
|
|
|
|
config.arguments.clone()
|
2016-10-29 00:14:41 -07:00
|
|
|
} else if let Some(recipe) = justfile.first() {
|
2017-05-07 15:11:10 -07:00
|
|
|
let min_arguments = recipe.min_arguments();
|
|
|
|
if min_arguments > 0 {
|
2018-12-08 14:29:41 -08:00
|
|
|
die!(
|
|
|
|
"Recipe `{}` cannot be used as default recipe since it requires at least {} argument{}.",
|
|
|
|
recipe.name,
|
|
|
|
min_arguments,
|
|
|
|
maybe_s(min_arguments)
|
|
|
|
);
|
2017-05-07 15:11:10 -07:00
|
|
|
}
|
|
|
|
vec![recipe.name]
|
2016-10-23 16:43:52 -07:00
|
|
|
} else {
|
2017-05-07 14:50:46 -07:00
|
|
|
die!("Justfile contains no recipes.");
|
2016-10-23 16:43:52 -07:00
|
|
|
};
|
|
|
|
|
2018-08-27 16:03:52 -07:00
|
|
|
if let Err(error) = InterruptHandler::install() {
|
|
|
|
warn!("Failed to set CTRL-C handler: {}", error)
|
|
|
|
}
|
|
|
|
|
2019-10-07 02:06:45 -07:00
|
|
|
if let Err(run_error) = justfile.run(&invocation_directory, &arguments, &config) {
|
|
|
|
if !config.quiet {
|
|
|
|
if config.color.stderr().active() {
|
2017-08-18 14:15:43 -07:00
|
|
|
eprintln!("{:#}", run_error);
|
2016-11-07 21:01:27 -08:00
|
|
|
} else {
|
2017-08-18 14:15:43 -07:00
|
|
|
eprintln!("{}", run_error);
|
2016-11-07 21:01:27 -08:00
|
|
|
}
|
2016-11-05 01:01:43 -07:00
|
|
|
}
|
2017-04-23 14:21:21 -07:00
|
|
|
|
2017-11-16 23:30:08 -08:00
|
|
|
process::exit(run_error.code().unwrap_or(EXIT_FAILURE));
|
2016-10-23 16:43:52 -07:00
|
|
|
}
|
|
|
|
}
|