From 8d80f83795834ee9d9f7b03c010295a6b5e77e7e Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Tue, 25 Jun 2024 09:51:57 -0700 Subject: [PATCH] group attribute on import https://github.com/casey/just/issues/2087 --- src/alias.rs | 2 +- src/analyzer.rs | 12 +++++- src/attribute.rs | 43 +++++++++++++++++++ src/compiler.rs | 1 + src/item.rs | 1 + src/lib.rs | 108 +++++++++++++++++++++++++++++++++++++---------- src/parser.rs | 45 ++++++++++++-------- src/recipe.rs | 1 + 8 files changed, 170 insertions(+), 43 deletions(-) diff --git a/src/alias.rs b/src/alias.rs index 286cefd..2f7072a 100644 --- a/src/alias.rs +++ b/src/alias.rs @@ -3,7 +3,7 @@ use super::*; /// An alias, e.g. `name := target` #[derive(Debug, PartialEq, Clone, Serialize)] pub(crate) struct Alias<'src, T = Rc>> { - pub(crate) attributes: BTreeSet>, + pub(crate) attributes: AttributeSet<'src>, pub(crate) name: Name<'src>, #[serde( bound(serialize = "T: Keyed<'src>"), diff --git a/src/analyzer.rs b/src/analyzer.rs index 9698284..1b8fd17 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -79,7 +79,15 @@ impl<'src> Analyzer<'src> { assignments.push(assignment); } Item::Comment(_) => (), - Item::Import { absolute, .. } => { + Item::Import { + absolute, + attributes, + .. + } => { + //TODO check attributes for validity + + let _groups = attributes.groups(); + if let Some(absolute) = absolute { stack.push(asts.get(absolute).unwrap()); } @@ -231,7 +239,7 @@ impl<'src> Analyzer<'src> { fn analyze_alias(alias: &Alias<'src, Name<'src>>) -> CompileResult<'src> { let name = alias.name.lexeme(); - for attribute in &alias.attributes { + for attribute in &alias.attributes.inner { if *attribute != Attribute::Private { return Err(alias.name.token.error(AliasInvalidAttribute { alias: name, diff --git a/src/attribute.rs b/src/attribute.rs index 5176364..263ae70 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -120,6 +120,49 @@ impl<'src> Display for Attribute<'src> { } } +#[derive(Debug, Clone, PartialEq, Serialize)] +pub(crate) struct AttributeSet<'src> { + #[serde(flatten)] + pub(crate) inner: BTreeSet>, +} + +impl<'src> AttributeSet<'src> { + pub(crate) fn empty() -> Self { + Self { + inner: BTreeSet::new(), + } + } + + pub(crate) fn from_map(input: BTreeMap, T>) -> Self { + Self { + inner: input.into_keys().collect(), + } + } + + pub(crate) fn to_btree_set(self) -> BTreeSet> { + self.inner + } + + pub(crate) fn contains(&self, attribute: &Attribute) -> bool { + self.inner.contains(attribute) + } + + /// Get the names of all Group attributes defined in this attribute set + pub(crate) fn groups(&self) -> Vec<&StringLiteral<'src>> { + self + .inner + .iter() + .filter_map(|attr| { + if let Attribute::Group(name) = attr { + Some(name) + } else { + None + } + }) + .collect() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/compiler.rs b/src/compiler.rs index 9eaf985..1d885a3 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -79,6 +79,7 @@ impl Compiler { absolute, optional, path, + attributes: _, } => { let import = current .path diff --git a/src/item.rs b/src/item.rs index b72ec8d..571625c 100644 --- a/src/item.rs +++ b/src/item.rs @@ -7,6 +7,7 @@ pub(crate) enum Item<'src> { Assignment(Assignment<'src>), Comment(&'src str), Import { + attributes: AttributeSet<'src>, absolute: Option, optional: bool, path: Token<'src>, diff --git a/src/lib.rs b/src/lib.rs index d787ab1..9047f65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,29 +21,91 @@ pub(crate) use { crate::{ - alias::Alias, analyzer::Analyzer, argument_parser::ArgumentParser, assignment::Assignment, - assignment_resolver::AssignmentResolver, ast::Ast, attribute::Attribute, binding::Binding, - color::Color, color_display::ColorDisplay, command_ext::CommandExt, compilation::Compilation, - compile_error::CompileError, compile_error_kind::CompileErrorKind, compiler::Compiler, - condition::Condition, conditional_operator::ConditionalOperator, config::Config, - config_error::ConfigError, constants::constants, count::Count, delimiter::Delimiter, - dependency::Dependency, dump_format::DumpFormat, enclosure::Enclosure, error::Error, - evaluator::Evaluator, execution_context::ExecutionContext, expression::Expression, - fragment::Fragment, function::Function, 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, shell::Shell, - show_whitespace::ShowWhitespace, source::Source, 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, use_color::UseColor, variables::Variables, - verbosity::Verbosity, warning::Warning, + alias::Alias, + analyzer::Analyzer, + argument_parser::ArgumentParser, + assignment::Assignment, + assignment_resolver::AssignmentResolver, + ast::Ast, + attribute::{Attribute, AttributeSet}, + binding::Binding, + color::Color, + color_display::ColorDisplay, + command_ext::CommandExt, + compilation::Compilation, + compile_error::CompileError, + compile_error_kind::CompileErrorKind, + compiler::Compiler, + condition::Condition, + conditional_operator::ConditionalOperator, + config::Config, + config_error::ConfigError, + constants::constants, + count::Count, + delimiter::Delimiter, + dependency::Dependency, + dump_format::DumpFormat, + enclosure::Enclosure, + error::Error, + evaluator::Evaluator, + execution_context::ExecutionContext, + expression::Expression, + fragment::Fragment, + function::Function, + 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, + shell::Shell, + show_whitespace::ShowWhitespace, + source::Source, + 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, + use_color::UseColor, + variables::Variables, + verbosity::Verbosity, + warning::Warning, }, camino::Utf8Path, derivative::Derivative, diff --git a/src/parser.rs b/src/parser.rs index 9ea372c..43fe165 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -334,7 +334,7 @@ impl<'run, 'src> Parser<'run, 'src> { } else if self.next_is(Identifier) { match Keyword::from_lexeme(next.lexeme()) { Some(Keyword::Alias) if self.next_are(&[Identifier, Identifier, ColonEquals]) => { - items.push(Item::Alias(self.parse_alias(BTreeSet::new())?)); + items.push(Item::Alias(self.parse_alias(AttributeSet::empty())?)); } Some(Keyword::Export) if self.next_are(&[Identifier, Identifier, ColonEquals]) => { self.presume_keyword(Keyword::Export)?; @@ -354,15 +354,7 @@ impl<'run, 'src> Parser<'run, 'src> { || self.next_are(&[Identifier, Identifier, StringToken]) || self.next_are(&[Identifier, QuestionMark]) => { - self.presume_keyword(Keyword::Import)?; - let optional = self.accepted(QuestionMark)?; - let (path, relative) = self.parse_string_literal_token()?; - items.push(Item::Import { - absolute: None, - optional, - path, - relative, - }); + items.push(self.parse_import(AttributeSet::empty())?); } Some(Keyword::Mod) if self.next_are(&[Identifier, Identifier, StringToken]) @@ -408,7 +400,7 @@ impl<'run, 'src> Parser<'run, 'src> { items.push(Item::Recipe(self.parse_recipe( doc, false, - BTreeSet::new(), + AttributeSet::empty(), )?)); } } @@ -418,7 +410,7 @@ impl<'run, 'src> Parser<'run, 'src> { items.push(Item::Recipe(self.parse_recipe( doc, true, - BTreeSet::new(), + AttributeSet::empty(), )?)); } else if let Some(attributes) = self.parse_attributes()? { let next_keyword = Keyword::from_lexeme(self.next()?.lexeme()); @@ -426,6 +418,13 @@ impl<'run, 'src> Parser<'run, 'src> { Some(Keyword::Alias) if self.next_are(&[Identifier, Identifier, ColonEquals]) => { items.push(Item::Alias(self.parse_alias(attributes)?)); } + Some(Keyword::Import) + if self.next_are(&[Identifier, StringToken]) + || self.next_are(&[Identifier, Identifier, StringToken]) + || self.next_are(&[Identifier, QuestionMark]) => + { + items.push(self.parse_import(attributes)?); + } _ => { let quiet = self.accepted(At)?; let doc = pop_doc_comment(&mut items, eol_since_last_comment); @@ -450,10 +449,22 @@ impl<'run, 'src> Parser<'run, 'src> { } } + fn parse_import(&mut self, attributes: AttributeSet<'src>) -> CompileResult<'src, Item<'src>> { + self.presume_keyword(Keyword::Import)?; + let optional = self.accepted(QuestionMark)?; + let (path, relative) = self.parse_string_literal_token()?; + Ok(Item::Import { + absolute: None, + attributes, + optional, + path, + relative, + }) + } /// Parse an alias, e.g `alias name := target` fn parse_alias( &mut self, - attributes: BTreeSet>, + attributes: AttributeSet<'src>, ) -> CompileResult<'src, Alias<'src, Name<'src>>> { self.presume_keyword(Keyword::Alias)?; let name = self.parse_name()?; @@ -745,7 +756,7 @@ impl<'run, 'src> Parser<'run, 'src> { &mut self, doc: Option<&'src str>, quiet: bool, - attributes: BTreeSet>, + attributes: AttributeSet<'src>, ) -> CompileResult<'src, UnresolvedRecipe<'src>> { let name = self.parse_name()?; @@ -807,7 +818,7 @@ impl<'run, 'src> Parser<'run, 'src> { Ok(Recipe { shebang: body.first().map_or(false, Line::is_shebang), - attributes, + attributes: attributes.to_btree_set(), body, dependencies, doc, @@ -984,7 +995,7 @@ impl<'run, 'src> Parser<'run, 'src> { } /// Parse recipe attributes - fn parse_attributes(&mut self) -> CompileResult<'src, Option>>> { + fn parse_attributes(&mut self) -> CompileResult<'src, Option>> { let mut attributes = BTreeMap::new(); while self.accepted(BracketL)? { @@ -1024,7 +1035,7 @@ impl<'run, 'src> Parser<'run, 'src> { if attributes.is_empty() { Ok(None) } else { - Ok(Some(attributes.into_keys().collect())) + Ok(Some(AttributeSet::from_map(attributes))) } } } diff --git a/src/recipe.rs b/src/recipe.rs index a41ce93..ca8cdee 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -19,6 +19,7 @@ fn error_from_signal(recipe: &str, line_number: Option, exit_status: Exit /// A recipe, e.g. `foo: bar baz` #[derive(PartialEq, Debug, Clone, Serialize)] pub(crate) struct Recipe<'src, D = Dependency<'src>> { + //TODO make this be attributeset too pub(crate) attributes: BTreeSet>, pub(crate) body: Vec>, pub(crate) dependencies: Vec,