1
1
mirror of https://github.com/casey/just.git synced 2024-11-26 11:43:43 +03:00
This commit is contained in:
Greg Shuflin 2024-11-03 20:26:50 -08:00 committed by GitHub
commit 0b64afdbd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 220 additions and 108 deletions

View File

@ -3,7 +3,7 @@ use super::*;
/// An alias, e.g. `name := target` /// An alias, e.g. `name := target`
#[derive(Debug, PartialEq, Clone, Serialize)] #[derive(Debug, PartialEq, Clone, Serialize)]
pub(crate) struct Alias<'src, T = Rc<Recipe<'src>>> { pub(crate) struct Alias<'src, T = Rc<Recipe<'src>>> {
pub(crate) attributes: BTreeSet<Attribute<'src>>, pub(crate) attributes: AttributeSet<'src>,
pub(crate) name: Name<'src>, pub(crate) name: Name<'src>,
#[serde( #[serde(
bound(serialize = "T: Keyed<'src>"), bound(serialize = "T: Keyed<'src>"),
@ -26,7 +26,7 @@ impl<'src> Alias<'src, Name<'src>> {
impl Alias<'_> { impl Alias<'_> {
pub(crate) fn is_private(&self) -> bool { pub(crate) fn is_private(&self) -> bool {
self.name.lexeme().starts_with('_') || self.attributes.contains(&Attribute::Private) self.name.lexeme().starts_with('_') || self.attributes.contains(AttributeDiscriminant::Private)
} }
} }

View File

@ -72,17 +72,21 @@ impl<'run, 'src> Analyzer<'run, 'src> {
} => { } => {
let mut doc_attr: Option<&str> = None; let mut doc_attr: Option<&str> = None;
let mut groups = Vec::new(); let mut groups = Vec::new();
for attribute in attributes { attributes.ensure_valid_attributes(
if let Attribute::Doc(ref doc) = attribute { "Module",
doc_attr = Some(doc.as_ref().map(|s| s.cooked.as_ref()).unwrap_or_default()); **name,
} else if let Attribute::Group(ref group) = attribute { &[AttributeDiscriminant::Doc, AttributeDiscriminant::Group],
groups.push(group.cooked.clone()); )?;
} else {
return Err(name.token.error(InvalidAttribute { for attribute in attributes.iter() {
item_kind: "Module", match attribute {
item_name: name.lexeme(), Attribute::Doc(ref doc) => {
attribute: attribute.clone(), doc_attr = Some(doc.as_ref().map(|s| s.cooked.as_ref()).unwrap_or_default());
})); }
Attribute::Group(ref group) => {
groups.push(group.cooked.clone());
}
_ => unreachable!(),
} }
} }
@ -170,11 +174,9 @@ impl<'run, 'src> Analyzer<'run, 'src> {
} }
for recipe in recipes.values() { for recipe in recipes.values() {
for attribute in &recipe.attributes { if recipe.attributes.contains(AttributeDiscriminant::Script) {
if let Attribute::Script(_) = attribute { unstable_features.insert(UnstableFeature::ScriptAttribute);
unstable_features.insert(UnstableFeature::ScriptAttribute); break;
break;
}
} }
} }
@ -284,11 +286,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
} }
if !recipe.is_script() { if !recipe.is_script() {
if let Some(attribute) = recipe if let Some(attribute) = recipe.attributes.get(AttributeDiscriminant::Extension) {
.attributes
.iter()
.find(|attribute| matches!(attribute, Attribute::Extension(_)))
{
return Err(recipe.name.error(InvalidAttribute { return Err(recipe.name.error(InvalidAttribute {
item_kind: "Recipe", item_kind: "Recipe",
item_name: recipe.name.lexeme(), item_name: recipe.name.lexeme(),
@ -301,16 +299,11 @@ impl<'run, 'src> Analyzer<'run, 'src> {
} }
fn analyze_alias(alias: &Alias<'src, Name<'src>>) -> CompileResult<'src> { fn analyze_alias(alias: &Alias<'src, Name<'src>>) -> CompileResult<'src> {
for attribute in &alias.attributes { alias.attributes.ensure_valid_attributes(
if *attribute != Attribute::Private { "Alias",
return Err(alias.name.token.error(InvalidAttribute { *alias.name,
item_kind: "Alias", &[AttributeDiscriminant::Private],
item_name: alias.name.lexeme(), )?;
attribute: attribute.clone(),
}));
}
}
Ok(()) Ok(())
} }

View File

@ -1,3 +1,5 @@
use std::collections::{self};
use super::*; use super::*;
#[derive( #[derive(
@ -96,6 +98,10 @@ impl<'src> Attribute<'src> {
}) })
} }
pub(crate) fn discriminant(&self) -> AttributeDiscriminant {
self.into()
}
pub(crate) fn name(&self) -> &'static str { pub(crate) fn name(&self) -> &'static str {
self.into() self.into()
} }
@ -129,6 +135,63 @@ impl<'src> Display for Attribute<'src> {
} }
} }
#[derive(Default, Debug, Clone, PartialEq, Serialize)]
pub(crate) struct AttributeSet<'src>(BTreeSet<Attribute<'src>>);
impl<'src, 'a> IntoIterator for &'a AttributeSet<'src> {
type Item = &'a Attribute<'src>;
type IntoIter = collections::btree_set::Iter<'a, Attribute<'src>>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'src> AttributeSet<'src> {
pub(crate) fn from_iter(iter: impl IntoIterator<Item = Attribute<'src>>) -> Self {
Self(iter.into_iter().collect())
}
pub(crate) fn len(&self) -> usize {
self.0.len()
}
pub(crate) fn contains(&self, target: AttributeDiscriminant) -> bool {
self.0.iter().any(|attr| attr.discriminant() == target)
}
pub(crate) fn get(&self, discriminant: AttributeDiscriminant) -> Option<&Attribute<'src>> {
self
.0
.iter()
.find(|attr| discriminant == attr.discriminant())
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &Attribute<'src>> {
self.0.iter()
}
pub(crate) fn ensure_valid_attributes(
&self,
item_kind: &'static str,
item_token: Token<'src>,
valid: &[AttributeDiscriminant],
) -> Result<(), CompileError<'src>> {
for attribute in &self.0 {
let discriminant = attribute.discriminant();
if !valid.contains(&discriminant) {
return Err(item_token.error(CompileErrorKind::InvalidAttribute {
item_kind,
item_name: item_token.lexeme(),
attribute: attribute.clone(),
}));
}
}
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -13,7 +13,7 @@ pub(crate) enum Item<'src> {
relative: StringLiteral<'src>, relative: StringLiteral<'src>,
}, },
Module { Module {
attributes: BTreeSet<Attribute<'src>>, attributes: AttributeSet<'src>,
absolute: Option<PathBuf>, absolute: Option<PathBuf>,
doc: Option<&'src str>, doc: Option<&'src str>,
name: Name<'src>, name: Name<'src>,

View File

@ -6,31 +6,95 @@
pub(crate) use { pub(crate) use {
crate::{ crate::{
alias::Alias, analyzer::Analyzer, argument_parser::ArgumentParser, assignment::Assignment, alias::Alias,
assignment_resolver::AssignmentResolver, ast::Ast, attribute::Attribute, binding::Binding, analyzer::Analyzer,
color::Color, color_display::ColorDisplay, command_color::CommandColor, argument_parser::ArgumentParser,
command_ext::CommandExt, compilation::Compilation, compile_error::CompileError, assignment::Assignment,
compile_error_kind::CompileErrorKind, compiler::Compiler, condition::Condition, assignment_resolver::AssignmentResolver,
conditional_operator::ConditionalOperator, config::Config, config_error::ConfigError, ast::Ast,
constants::constants, count::Count, delimiter::Delimiter, dependency::Dependency, attribute::{Attribute, AttributeDiscriminant, AttributeSet},
dump_format::DumpFormat, enclosure::Enclosure, error::Error, evaluator::Evaluator, binding::Binding,
execution_context::ExecutionContext, executor::Executor, expression::Expression, color::Color,
fragment::Fragment, function::Function, interpreter::Interpreter, color_display::ColorDisplay,
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item, command_color::CommandColor,
justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List, command_ext::CommandExt,
load_dotenv::load_dotenv, loader::Loader, module_path::ModulePath, name::Name, compilation::Compilation,
namepath::Namepath, ordinal::Ordinal, output::output, output_error::OutputError, compile_error::CompileError,
parameter::Parameter, parameter_kind::ParameterKind, parser::Parser, platform::Platform, compile_error_kind::CompileErrorKind,
platform_interface::PlatformInterface, position::Position, positional::Positional, ran::Ran, compiler::Compiler,
range_ext::RangeExt, recipe::Recipe, recipe_resolver::RecipeResolver, condition::Condition,
recipe_signature::RecipeSignature, scope::Scope, search::Search, search_config::SearchConfig, conditional_operator::ConditionalOperator,
search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang, config::Config,
show_whitespace::ShowWhitespace, source::Source, string_delimiter::StringDelimiter, config_error::ConfigError,
string_kind::StringKind, string_literal::StringLiteral, subcommand::Subcommand, constants::constants,
suggestion::Suggestion, table::Table, thunk::Thunk, token::Token, token_kind::TokenKind, count::Count,
unresolved_dependency::UnresolvedDependency, unresolved_recipe::UnresolvedRecipe, delimiter::Delimiter,
unstable_feature::UnstableFeature, use_color::UseColor, variables::Variables, dependency::Dependency,
verbosity::Verbosity, warning::Warning, dump_format::DumpFormat,
enclosure::Enclosure,
error::Error,
evaluator::Evaluator,
execution_context::ExecutionContext,
executor::Executor,
expression::Expression,
fragment::Fragment,
function::Function,
interpreter::Interpreter,
interrupt_guard::InterruptGuard,
interrupt_handler::InterruptHandler,
item::Item,
justfile::Justfile,
keyed::Keyed,
keyword::Keyword,
lexer::Lexer,
line::Line,
list::List,
load_dotenv::load_dotenv,
loader::Loader,
module_path::ModulePath,
name::Name,
namepath::Namepath,
ordinal::Ordinal,
output::output,
output_error::OutputError,
parameter::Parameter,
parameter_kind::ParameterKind,
parser::Parser,
platform::Platform,
platform_interface::PlatformInterface,
position::Position,
positional::Positional,
ran::Ran,
range_ext::RangeExt,
recipe::Recipe,
recipe_resolver::RecipeResolver,
recipe_signature::RecipeSignature,
scope::Scope,
search::Search,
search_config::SearchConfig,
search_error::SearchError,
set::Set,
setting::Setting,
settings::Settings,
shebang::Shebang,
show_whitespace::ShowWhitespace,
source::Source,
string_delimiter::StringDelimiter,
string_kind::StringKind,
string_literal::StringLiteral,
subcommand::Subcommand,
suggestion::Suggestion,
table::Table,
thunk::Thunk,
token::Token,
token_kind::TokenKind,
unresolved_dependency::UnresolvedDependency,
unresolved_recipe::UnresolvedRecipe,
unstable_feature::UnstableFeature,
use_color::UseColor,
variables::Variables,
verbosity::Verbosity,
warning::Warning,
}, },
camino::Utf8Path, camino::Utf8Path,
clap::ValueEnum, clap::ValueEnum,

View File

@ -462,7 +462,7 @@ impl<'run, 'src> Parser<'run, 'src> {
/// Parse an alias, e.g `alias name := target` /// Parse an alias, e.g `alias name := target`
fn parse_alias( fn parse_alias(
&mut self, &mut self,
attributes: BTreeSet<Attribute<'src>>, attributes: AttributeSet<'src>,
) -> CompileResult<'src, Alias<'src, Name<'src>>> { ) -> CompileResult<'src, Alias<'src, Name<'src>>> {
self.presume_keyword(Keyword::Alias)?; self.presume_keyword(Keyword::Alias)?;
let name = self.parse_name()?; let name = self.parse_name()?;
@ -480,24 +480,15 @@ impl<'run, 'src> Parser<'run, 'src> {
fn parse_assignment( fn parse_assignment(
&mut self, &mut self,
export: bool, export: bool,
attributes: BTreeSet<Attribute<'src>>, attributes: AttributeSet<'src>,
) -> CompileResult<'src, Assignment<'src>> { ) -> CompileResult<'src, Assignment<'src>> {
let name = self.parse_name()?; let name = self.parse_name()?;
self.presume(ColonEquals)?; self.presume(ColonEquals)?;
let value = self.parse_expression()?; let value = self.parse_expression()?;
self.expect_eol()?; self.expect_eol()?;
let private = attributes.contains(&Attribute::Private); let private = attributes.contains(AttributeDiscriminant::Private);
attributes.ensure_valid_attributes("Assignment", *name, &[AttributeDiscriminant::Private])?;
for attribute in attributes {
if attribute != Attribute::Private {
return Err(name.error(CompileErrorKind::InvalidAttribute {
item_kind: "Assignment",
item_name: name.lexeme(),
attribute,
}));
}
}
Ok(Assignment { Ok(Assignment {
constant: false, constant: false,
@ -863,7 +854,7 @@ impl<'run, 'src> Parser<'run, 'src> {
&mut self, &mut self,
doc: Option<&'src str>, doc: Option<&'src str>,
quiet: bool, quiet: bool,
attributes: BTreeSet<Attribute<'src>>, attributes: AttributeSet<'src>,
) -> CompileResult<'src, UnresolvedRecipe<'src>> { ) -> CompileResult<'src, UnresolvedRecipe<'src>> {
let name = self.parse_name()?; let name = self.parse_name()?;
@ -924,9 +915,7 @@ impl<'run, 'src> Parser<'run, 'src> {
let body = self.parse_body()?; let body = self.parse_body()?;
let shebang = body.first().map_or(false, Line::is_shebang); let shebang = body.first().map_or(false, Line::is_shebang);
let script = attributes let script = attributes.contains(AttributeDiscriminant::Script);
.iter()
.any(|attribute| matches!(attribute, Attribute::Script(_)));
if shebang && script { if shebang && script {
return Err(name.error(CompileErrorKind::ShebangAndScriptAttribute { return Err(name.error(CompileErrorKind::ShebangAndScriptAttribute {
@ -934,7 +923,8 @@ impl<'run, 'src> Parser<'run, 'src> {
})); }));
} }
let private = name.lexeme().starts_with('_') || attributes.contains(&Attribute::Private); let private =
name.lexeme().starts_with('_') || attributes.contains(AttributeDiscriminant::Private);
Ok(Recipe { Ok(Recipe {
shebang: shebang || script, shebang: shebang || script,
@ -1114,9 +1104,7 @@ impl<'run, 'src> Parser<'run, 'src> {
} }
/// Item attributes, i.e., `[macos]` or `[confirm: "warning!"]` /// Item attributes, i.e., `[macos]` or `[confirm: "warning!"]`
fn parse_attributes( fn parse_attributes(&mut self) -> CompileResult<'src, Option<(Token<'src>, AttributeSet<'src>)>> {
&mut self,
) -> CompileResult<'src, Option<(Token<'src>, BTreeSet<Attribute<'src>>)>> {
let mut attributes = BTreeMap::new(); let mut attributes = BTreeMap::new();
let mut token = None; let mut token = None;
@ -1164,7 +1152,8 @@ impl<'run, 'src> Parser<'run, 'src> {
if attributes.is_empty() { if attributes.is_empty() {
Ok(None) Ok(None)
} else { } else {
Ok(Some((token.unwrap(), attributes.into_keys().collect()))) let attribute_set = AttributeSet::from_iter(attributes.into_keys());
Ok(Some((token.unwrap(), attribute_set)))
} }
} }
} }

View File

@ -19,7 +19,7 @@ fn error_from_signal(recipe: &str, line_number: Option<usize>, exit_status: Exit
/// A recipe, e.g. `foo: bar baz` /// A recipe, e.g. `foo: bar baz`
#[derive(PartialEq, Debug, Clone, Serialize)] #[derive(PartialEq, Debug, Clone, Serialize)]
pub(crate) struct Recipe<'src, D = Dependency<'src>> { pub(crate) struct Recipe<'src, D = Dependency<'src>> {
pub(crate) attributes: BTreeSet<Attribute<'src>>, pub(crate) attributes: AttributeSet<'src>,
pub(crate) body: Vec<Line<'src>>, pub(crate) body: Vec<Line<'src>>,
pub(crate) dependencies: Vec<D>, pub(crate) dependencies: Vec<D>,
pub(crate) doc: Option<&'src str>, pub(crate) doc: Option<&'src str>,
@ -66,20 +66,20 @@ impl<'src, D> Recipe<'src, D> {
} }
pub(crate) fn confirm(&self) -> RunResult<'src, bool> { pub(crate) fn confirm(&self) -> RunResult<'src, bool> {
for attribute in &self.attributes { if let Some(Attribute::Confirm(ref prompt)) =
if let Attribute::Confirm(prompt) = attribute { self.attributes.get(AttributeDiscriminant::Confirm)
if let Some(prompt) = prompt { {
eprint!("{} ", prompt.cooked); if let Some(prompt) = prompt {
} else { eprint!("{} ", prompt.cooked);
eprint!("Run recipe `{}`? ", self.name); } else {
} eprint!("Run recipe `{}`? ", self.name);
let mut line = String::new();
std::io::stdin()
.read_line(&mut line)
.map_err(|io_error| Error::GetConfirmation { io_error })?;
let line = line.trim().to_lowercase();
return Ok(line == "y" || line == "yes");
} }
let mut line = String::new();
std::io::stdin()
.read_line(&mut line)
.map_err(|io_error| Error::GetConfirmation { io_error })?;
let line = line.trim().to_lowercase();
return Ok(line == "y" || line == "yes");
} }
Ok(true) Ok(true)
} }
@ -97,7 +97,7 @@ impl<'src, D> Recipe<'src, D> {
} }
pub(crate) fn is_public(&self) -> bool { pub(crate) fn is_public(&self) -> bool {
!self.private && !self.attributes.contains(&Attribute::Private) !self.private && !self.attributes.contains(AttributeDiscriminant::Private)
} }
pub(crate) fn is_script(&self) -> bool { pub(crate) fn is_script(&self) -> bool {
@ -105,18 +105,21 @@ impl<'src, D> Recipe<'src, D> {
} }
pub(crate) fn takes_positional_arguments(&self, settings: &Settings) -> bool { pub(crate) fn takes_positional_arguments(&self, settings: &Settings) -> bool {
settings.positional_arguments || self.attributes.contains(&Attribute::PositionalArguments) settings.positional_arguments
|| self
.attributes
.contains(AttributeDiscriminant::PositionalArguments)
} }
pub(crate) fn change_directory(&self) -> bool { pub(crate) fn change_directory(&self) -> bool {
!self.attributes.contains(&Attribute::NoCd) !self.attributes.contains(AttributeDiscriminant::NoCd)
} }
pub(crate) fn enabled(&self) -> bool { pub(crate) fn enabled(&self) -> bool {
let windows = self.attributes.contains(&Attribute::Windows); let windows = self.attributes.contains(AttributeDiscriminant::Windows);
let linux = self.attributes.contains(&Attribute::Linux); let linux = self.attributes.contains(AttributeDiscriminant::Linux);
let macos = self.attributes.contains(&Attribute::Macos); let macos = self.attributes.contains(AttributeDiscriminant::Macos);
let unix = self.attributes.contains(&Attribute::Unix); let unix = self.attributes.contains(AttributeDiscriminant::Unix);
(!windows && !linux && !macos && !unix) (!windows && !linux && !macos && !unix)
|| (cfg!(target_os = "windows") && windows) || (cfg!(target_os = "windows") && windows)
@ -127,7 +130,9 @@ impl<'src, D> Recipe<'src, D> {
} }
fn print_exit_message(&self) -> bool { fn print_exit_message(&self) -> bool {
!self.attributes.contains(&Attribute::NoExitMessage) !self
.attributes
.contains(AttributeDiscriminant::NoExitMessage)
} }
fn working_directory<'a>(&'a self, context: &'a ExecutionContext) -> Option<PathBuf> { fn working_directory<'a>(&'a self, context: &'a ExecutionContext) -> Option<PathBuf> {
@ -139,7 +144,7 @@ impl<'src, D> Recipe<'src, D> {
} }
fn no_quiet(&self) -> bool { fn no_quiet(&self) -> bool {
self.attributes.contains(&Attribute::NoQuiet) self.attributes.contains(AttributeDiscriminant::NoQuiet)
} }
pub(crate) fn run<'run>( pub(crate) fn run<'run>(
@ -341,10 +346,8 @@ impl<'src, D> Recipe<'src, D> {
return Ok(()); return Ok(());
} }
let executor = if let Some(Attribute::Script(interpreter)) = self let executor = if let Some(Attribute::Script(interpreter)) =
.attributes self.attributes.get(AttributeDiscriminant::Script)
.iter()
.find(|attribute| matches!(attribute, Attribute::Script(_)))
{ {
Executor::Command( Executor::Command(
interpreter interpreter