Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement loading justfile from stdin #1933

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ pub(crate) enum Error<'src> {
},
NoChoosableRecipes,
NoDefaultRecipe,
JustfileIsNotAFile,
NoRecipes,
NotConfirmed {
recipe: &'src str,
Expand Down Expand Up @@ -403,6 +404,7 @@ impl<'src> ColorDisplay for Error<'src> {
_ => write!(f, "Recipe `{recipe}` could not be run because of an IO error while launching the shell: {io_error}"),
}?;
}
JustfileIsNotAFile => write!(f, "Justfile is not a file.")?,
Load { io_error, path } => {
write!(f, "Failed to read justfile at `{}`: {io_error}", path.display())?;
}
Expand Down
20 changes: 16 additions & 4 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,12 +397,14 @@ fn justfile(context: Context) -> FunctionResult {
.context
.search
.justfile
.as_ref()
.ok_or(String::from("Justfile is not a file"))?
.to_str()
.map(str::to_owned)
.ok_or_else(|| {
format!(
"Justfile path is not valid unicode: {}",
context.evaluator.context.search.justfile.display()
"Justfile path is not valid unicode: {:?}",
context.evaluator.context.search.justfile
)
})
}
Expand All @@ -413,11 +415,13 @@ fn justfile_directory(context: Context) -> FunctionResult {
.context
.search
.justfile
.as_ref()
.ok_or(String::from("Justfile is not a file"))?
.parent()
.ok_or_else(|| {
format!(
"Could not resolve justfile directory. Justfile `{}` had no parent.",
context.evaluator.context.search.justfile.display()
"Could not resolve justfile directory. Justfile `{:?}` had no parent.",
context.evaluator.context.search.justfile
)
})?;

Expand Down Expand Up @@ -450,6 +454,8 @@ fn module_directory(context: Context) -> FunctionResult {
.context
.search
.justfile
.as_ref()
.ok_or(String::from("Module is not a file"))?
.parent()
.unwrap()
.join(&context.evaluator.context.module.source)
Expand Down Expand Up @@ -478,6 +484,8 @@ fn module_file(context: Context) -> FunctionResult {
.context
.search
.justfile
.as_ref()
.ok_or(String::from("Module is not a file"))?
.parent()
.unwrap()
.join(&context.evaluator.context.module.source)
Expand Down Expand Up @@ -589,6 +597,8 @@ fn source_directory(context: Context) -> FunctionResult {
.context
.search
.justfile
.as_ref()
.ok_or(String::from("Source is not a file"))?
.parent()
.unwrap()
.join(context.name.token.path)
Expand All @@ -610,6 +620,8 @@ fn source_file(context: Context) -> FunctionResult {
.context
.search
.justfile
.as_ref()
.ok_or(String::from("Source is not a file"))?
.parent()
.unwrap()
.join(context.name.token.path)
Expand Down
55 changes: 23 additions & 32 deletions src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const PROJECT_ROOT_CHILDREN: &[&str] = &[".bzr", ".git", ".hg", ".svn", "_darcs"

#[derive(Debug)]
pub(crate) struct Search {
pub(crate) justfile: PathBuf,
pub(crate) justfile: Option<PathBuf>,
pub(crate) working_directory: PathBuf,
}

Expand Down Expand Up @@ -51,15 +51,15 @@ impl Search {
})
}
SearchConfig::GlobalJustfile => Ok(Self {
justfile: Self::global_justfile_paths()
justfile: Some(Self::global_justfile_paths()
.iter()
.find(|path| path.exists())
.cloned()
.ok_or(SearchError::GlobalJustfileNotFound)?,
.ok_or(SearchError::GlobalJustfileNotFound)?),
working_directory: Self::project_root(invocation_directory)?,
}),
SearchConfig::WithJustfile { justfile } => {
let justfile = Self::clean(invocation_directory, justfile);
let justfile = Some(Self::clean(invocation_directory, justfile));
let working_directory = Self::working_directory_from_justfile(&justfile)?;
Ok(Self {
justfile,
Expand All @@ -70,20 +70,20 @@ impl Search {
justfile,
working_directory,
} => Ok(Self {
justfile: Self::clean(invocation_directory, justfile),
justfile: Some(Self::clean(invocation_directory, justfile)),
working_directory: Self::clean(invocation_directory, working_directory),
}),
}
}

/// Find justfile starting from parent directory of current justfile
pub(crate) fn search_parent_directory(&self) -> SearchResult<Self> {
let parent = self
.justfile
let justfile = self.justfile.as_ref().ok_or(SearchError::JustfileIsNotAFile)?;
let parent = justfile
.parent()
.and_then(|path| path.parent())
.ok_or_else(|| SearchError::JustfileHadNoParent {
path: self.justfile.clone(),
path: justfile.clone(),
})?;
Self::find_in_directory(parent)
}
Expand All @@ -102,47 +102,37 @@ impl Search {
pub(crate) fn init(
search_config: &SearchConfig,
invocation_directory: &Path,
) -> SearchResult<Self> {
) -> SearchResult<PathBuf> {
match search_config {
SearchConfig::FromInvocationDirectory => {
let working_directory = Self::project_root(invocation_directory)?;
let justfile = working_directory.join(DEFAULT_JUSTFILE_NAME);
Ok(Self {
justfile,
working_directory,
})
Ok(justfile)
}
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(DEFAULT_JUSTFILE_NAME);
Ok(Self {
justfile,
working_directory,
})
Ok(justfile)
}
SearchConfig::GlobalJustfile => Err(SearchError::GlobalJustfileInit),
SearchConfig::WithJustfile { justfile } => {
let justfile = Self::clean(invocation_directory, justfile);
let working_directory = Self::working_directory_from_justfile(&justfile)?;
Ok(Self {
justfile,
working_directory,
})
Ok(justfile)
}
SearchConfig::WithJustfileAndWorkingDirectory {
justfile,
working_directory,
} => Ok(Self {
justfile: Self::clean(invocation_directory, justfile),
working_directory: Self::clean(invocation_directory, working_directory),
}),
working_directory: _,
} => {
let justfile = Self::clean(invocation_directory, justfile);
Ok(justfile)
}
}
}

/// Search upwards from `directory` for a file whose name matches one of
/// `JUSTFILE_NAMES`
fn justfile(directory: &Path) -> SearchResult<PathBuf> {
fn justfile(directory: &Path) -> SearchResult<Option<PathBuf>> {
for directory in directory.ancestors() {
let mut candidates = BTreeSet::new();

Expand All @@ -166,7 +156,7 @@ impl Search {

match candidates.len() {
0 => {}
1 => return Ok(candidates.into_iter().next().unwrap()),
1 => return Ok(candidates.pop_first()),
_ => return Err(SearchError::MultipleCandidates { candidates }),
}
}
Expand Down Expand Up @@ -218,7 +208,8 @@ impl Search {
Ok(directory.to_owned())
}

fn working_directory_from_justfile(justfile: &Path) -> SearchResult<PathBuf> {
fn working_directory_from_justfile(justfile: &Option<impl AsRef<Path>>) -> SearchResult<PathBuf> {
let justfile = justfile.as_ref().ok_or(SearchError::JustfileIsNotAFile)?.as_ref();
Ok(
justfile
.parent()
Expand Down Expand Up @@ -333,7 +324,7 @@ mod tests {
Ok(found_path) => {
path.pop();
path.push(DEFAULT_JUSTFILE_NAME);
assert_eq!(found_path, path);
assert_eq!(found_path, Some(path));
}
Err(err) => panic!("No errors were expected: {err}"),
}
Expand All @@ -360,7 +351,7 @@ mod tests {

let search = Search::find(&search_config, &sub).unwrap();

assert_eq!(search.justfile, justfile);
assert_eq!(search.justfile.unwrap(), justfile);
assert_eq!(search.working_directory, sub);
}

Expand Down
2 changes: 2 additions & 0 deletions src/search_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub(crate) enum SearchError {
directory: PathBuf,
io_error: io::Error,
},
#[snafu(display("Justfile is not a file"))]
JustfileIsNotAFile,
#[snafu(display("Justfile path had no parent: {}", path.display()))]
JustfileHadNoParent { path: PathBuf },
#[snafu(display(
Expand Down
38 changes: 23 additions & 15 deletions src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ impl Subcommand {
arguments: &[String],
overrides: &BTreeMap<String, String>,
) -> RunResult<'src> {
let starting_parent = search.justfile.parent().as_ref().unwrap().lexiclean();
let justfile = search.justfile.as_ref().ok_or(Error::JustfileIsNotAFile)?;
let starting_parent = justfile.parent().as_ref().unwrap().lexiclean();

loop {
let justfile = &compilation.justfile;
Expand All @@ -119,17 +120,18 @@ impl Subcommand {
if fallback {
if let Err(err @ (Error::UnknownRecipe { .. } | Error::UnknownSubmodule { .. })) = result {
search = search.search_parent_directory().map_err(|_| err)?;
let justfile = search.justfile.as_ref().ok_or(Error::JustfileIsNotAFile)?;

if config.verbosity.loquacious() {
eprintln!(
"Trying {}",
starting_parent
.strip_prefix(search.justfile.parent().unwrap())
.strip_prefix(justfile.parent().unwrap())
.unwrap()
.components()
.map(|_| path::Component::ParentDir)
.collect::<PathBuf>()
.join(search.justfile.file_name().unwrap())
.join(justfile.file_name().unwrap())
.display()
);
}
Expand All @@ -149,7 +151,9 @@ impl Subcommand {
loader: &'src Loader,
search: &Search,
) -> RunResult<'src, Compilation<'src>> {
let compilation = Compiler::compile(loader, &search.justfile)?;
let justfile = search.justfile.as_ref().ok_or(Error::JustfileIsNotAFile)?;

let compilation = Compiler::compile(loader, justfile)?;

compilation.justfile.check_unstable(config)?;

Expand Down Expand Up @@ -192,9 +196,10 @@ impl Subcommand {
let chooser = if let Some(chooser) = chooser {
OsString::from(chooser)
} else {
let justfile = search.justfile.as_ref().ok_or(Error::JustfileIsNotAFile)?;
let mut chooser = OsString::new();
chooser.push("fzf --multi --preview 'just --unstable --color always --justfile \"");
chooser.push(&search.justfile);
chooser.push(justfile);
chooser.push("\" --show {}'");
chooser
};
Expand Down Expand Up @@ -279,9 +284,10 @@ impl Subcommand {
.or_else(|| env::var_os("EDITOR"))
.unwrap_or_else(|| "vim".into());

let justfile = search.justfile.as_ref().ok_or(Error::JustfileIsNotAFile)?;
let error = Command::new(&editor)
.current_dir(&search.working_directory)
.arg(&search.justfile)
.arg(&justfile)
.status();

let status = match error {
Expand Down Expand Up @@ -333,36 +339,38 @@ impl Subcommand {
};
}

fs::write(&search.justfile, formatted).map_err(|io_error| Error::WriteJustfile {
justfile: search.justfile.clone(),
let justfile = search.justfile.as_ref().ok_or(Error::JustfileIsNotAFile)?;

fs::write(justfile, formatted).map_err(|io_error| Error::WriteJustfile {
justfile: justfile.clone(),
io_error,
})?;

if config.verbosity.loud() {
eprintln!("Wrote justfile to `{}`", search.justfile.display());
eprintln!("Wrote justfile to `{}`", justfile.display());
}

Ok(())
}

fn init(config: &Config) -> RunResult<'static> {
let search = Search::init(&config.search_config, &config.invocation_directory)?;
let justfile = Search::init(&config.search_config, &config.invocation_directory)?;

if search.justfile.is_file() {
if justfile.is_file() {
return Err(Error::InitExists {
justfile: search.justfile,
justfile: justfile.clone(),
});
}

if let Err(io_error) = fs::write(&search.justfile, INIT_JUSTFILE) {
if let Err(io_error) = fs::write(&justfile, INIT_JUSTFILE) {
return Err(Error::WriteJustfile {
justfile: search.justfile,
justfile: justfile.clone(),
io_error,
});
}

if config.verbosity.loud() {
eprintln!("Wrote justfile to `{}`", search.justfile.display());
eprintln!("Wrote justfile to `{}`", justfile.display());
}

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub(crate) fn search(config: &Config) -> Search {
let justfile = working_directory.join("justfile");

Search {
justfile,
justfile: Some(justfile),
working_directory,
}
}
Expand Down
1 change: 1 addition & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ mod shell;
mod shell_expansion;
mod show;
mod slash_operator;
mod stdin;
mod string;
mod subsequents;
mod summary;
Expand Down
Loading