Skip to content

Commit c36db49

Browse files
committed
not() attribute
#1895
1 parent 20192d4 commit c36db49

File tree

7 files changed

+204
-46
lines changed

7 files changed

+204
-46
lines changed

src/attribute.rs

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ pub(crate) enum Attribute<'src> {
1313
Doc(Option<StringLiteral<'src>>),
1414
Extension(StringLiteral<'src>),
1515
Group(StringLiteral<'src>),
16-
Linux,
17-
Macos,
16+
Linux { inverted: bool },
17+
Macos { inverted: bool },
1818
NoCd,
1919
NoExitMessage,
2020
NoQuiet,
21-
Openbsd,
21+
Openbsd { inverted: bool },
2222
PositionalArguments,
2323
Private,
2424
Script(Option<Interpreter<'src>>),
25-
Unix,
26-
Windows,
25+
Unix { inverted: bool },
26+
Windows { inverted: bool },
2727
WorkingDirectory(StringLiteral<'src>),
2828
}
2929

@@ -51,6 +51,7 @@ impl<'src> Attribute<'src> {
5151
pub(crate) fn new(
5252
name: Name<'src>,
5353
arguments: Vec<StringLiteral<'src>>,
54+
inverted: bool,
5455
) -> CompileResult<'src, Self> {
5556
let discriminant = name
5657
.lexeme()
@@ -75,29 +76,38 @@ impl<'src> Attribute<'src> {
7576
);
7677
}
7778

78-
Ok(match discriminant {
79-
AttributeDiscriminant::Confirm => Self::Confirm(arguments.into_iter().next()),
80-
AttributeDiscriminant::Doc => Self::Doc(arguments.into_iter().next()),
81-
AttributeDiscriminant::Extension => Self::Extension(arguments.into_iter().next().unwrap()),
82-
AttributeDiscriminant::Group => Self::Group(arguments.into_iter().next().unwrap()),
83-
AttributeDiscriminant::Linux => Self::Linux,
84-
AttributeDiscriminant::Macos => Self::Macos,
85-
AttributeDiscriminant::NoCd => Self::NoCd,
86-
AttributeDiscriminant::NoExitMessage => Self::NoExitMessage,
87-
AttributeDiscriminant::NoQuiet => Self::NoQuiet,
88-
AttributeDiscriminant::Openbsd => Self::Openbsd,
89-
AttributeDiscriminant::PositionalArguments => Self::PositionalArguments,
90-
AttributeDiscriminant::Private => Self::Private,
91-
AttributeDiscriminant::Script => Self::Script({
79+
Ok(match (inverted, discriminant) {
80+
(inverted, AttributeDiscriminant::Linux) => Self::Linux { inverted },
81+
(inverted, AttributeDiscriminant::Macos) => Self::Macos { inverted },
82+
(inverted, AttributeDiscriminant::Unix) => Self::Unix { inverted },
83+
(inverted, AttributeDiscriminant::Windows) => Self::Windows { inverted },
84+
(inverted, AttributeDiscriminant::Openbsd) => Self::Openbsd { inverted },
85+
86+
(true, _attr) => {
87+
return Err(name.error(CompileErrorKind::InvalidInvertedAttribute {
88+
attr_name: name.lexeme(),
89+
}))
90+
}
91+
92+
(false, AttributeDiscriminant::Confirm) => Self::Confirm(arguments.into_iter().next()),
93+
(false, AttributeDiscriminant::Doc) => Self::Doc(arguments.into_iter().next()),
94+
(false, AttributeDiscriminant::Extension) => {
95+
Self::Extension(arguments.into_iter().next().unwrap())
96+
}
97+
(false, AttributeDiscriminant::Group) => Self::Group(arguments.into_iter().next().unwrap()),
98+
(false, AttributeDiscriminant::NoCd) => Self::NoCd,
99+
(false, AttributeDiscriminant::NoExitMessage) => Self::NoExitMessage,
100+
(false, AttributeDiscriminant::NoQuiet) => Self::NoQuiet,
101+
(false, AttributeDiscriminant::PositionalArguments) => Self::PositionalArguments,
102+
(false, AttributeDiscriminant::Private) => Self::Private,
103+
(false, AttributeDiscriminant::Script) => Self::Script({
92104
let mut arguments = arguments.into_iter();
93105
arguments.next().map(|command| Interpreter {
94106
command,
95107
arguments: arguments.collect(),
96108
})
97109
}),
98-
AttributeDiscriminant::Unix => Self::Unix,
99-
AttributeDiscriminant::Windows => Self::Windows,
100-
AttributeDiscriminant::WorkingDirectory => {
110+
(false, AttributeDiscriminant::WorkingDirectory) => {
101111
Self::WorkingDirectory(arguments.into_iter().next().unwrap())
102112
}
103113
})
@@ -118,28 +128,34 @@ impl<'src> Attribute<'src> {
118128

119129
impl Display for Attribute<'_> {
120130
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
121-
write!(f, "{}", self.name())?;
131+
let name = self.name();
122132

123133
match self {
124134
Self::Confirm(Some(argument))
125135
| Self::Doc(Some(argument))
126136
| Self::Extension(argument)
127137
| Self::Group(argument)
128-
| Self::WorkingDirectory(argument) => write!(f, "({argument})")?,
129-
Self::Script(Some(shell)) => write!(f, "({shell})")?,
138+
| Self::WorkingDirectory(argument) => write!(f, "{name}({argument})")?,
139+
Self::Script(Some(shell)) => write!(f, "{name}({shell})")?,
140+
Self::Linux { inverted }
141+
| Self::Macos { inverted }
142+
| Self::Unix { inverted }
143+
| Self::Openbsd { inverted }
144+
| Self::Windows { inverted } => {
145+
if *inverted {
146+
write!(f, "not({name})")?;
147+
} else {
148+
write!(f, "{name}")?;
149+
}
150+
}
130151
Self::Confirm(None)
131152
| Self::Doc(None)
132-
| Self::Linux
133-
| Self::Macos
134153
| Self::NoCd
135154
| Self::NoExitMessage
136155
| Self::NoQuiet
137-
| Self::Openbsd
138156
| Self::PositionalArguments
139157
| Self::Private
140-
| Self::Script(None)
141-
| Self::Unix
142-
| Self::Windows => {}
158+
| Self::Script(None) => write!(f, "{name}")?,
143159
}
144160

145161
Ok(())

src/attribute_set.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
use {super::*, std::collections};
22

3+
#[derive(Debug, Copy, Clone)]
4+
pub(crate) enum InvertedStatus {
5+
Normal,
6+
Inverted,
7+
}
8+
39
#[derive(Default, Debug, Clone, PartialEq, Serialize)]
410
pub(crate) struct AttributeSet<'src>(BTreeSet<Attribute<'src>>);
511

@@ -12,6 +18,28 @@ impl<'src> AttributeSet<'src> {
1218
self.0.iter().any(|attr| attr.discriminant() == target)
1319
}
1420

21+
pub(crate) fn contains_invertible(
22+
&self,
23+
target: AttributeDiscriminant,
24+
) -> Option<InvertedStatus> {
25+
self.get(target).and_then(|attr| {
26+
Some(match attr {
27+
Attribute::Linux { inverted }
28+
| Attribute::Macos { inverted }
29+
| Attribute::Openbsd { inverted }
30+
| Attribute::Unix { inverted }
31+
| Attribute::Windows { inverted } => {
32+
if *inverted {
33+
InvertedStatus::Inverted
34+
} else {
35+
InvertedStatus::Normal
36+
}
37+
}
38+
_ => return None,
39+
})
40+
})
41+
}
42+
1543
pub(crate) fn get(&self, discriminant: AttributeDiscriminant) -> Option<&Attribute<'src>> {
1644
self
1745
.0

src/compile_error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ impl Display for CompileError<'_> {
186186
_ => character.escape_default().collect(),
187187
}
188188
),
189+
InvalidInvertedAttribute { attr_name } => {
190+
write!(f, "{attr_name} cannot be inverted with `not()`")
191+
}
189192
MismatchedClosingDelimiter {
190193
open,
191194
open_line,

src/compile_error_kind.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ pub(crate) enum CompileErrorKind<'src> {
7979
InvalidEscapeSequence {
8080
character: char,
8181
},
82+
InvalidInvertedAttribute {
83+
attr_name: &'src str,
84+
},
8285
MismatchedClosingDelimiter {
8386
close: Delimiter,
8487
open: Delimiter,

src/parser.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,7 +1135,18 @@ impl<'run, 'src> Parser<'run, 'src> {
11351135
token.get_or_insert(bracket);
11361136

11371137
loop {
1138-
let name = self.parse_name()?;
1138+
let (name, inverted) = {
1139+
let mut i = false;
1140+
let mut n = self.parse_name()?;
1141+
if n.lexeme() == "not" {
1142+
i = true;
1143+
self.expect(ParenL)?;
1144+
n = self.parse_name()?;
1145+
self.expect(ParenR)?;
1146+
}
1147+
1148+
(n, i)
1149+
};
11391150

11401151
let mut arguments = Vec::new();
11411152

@@ -1152,7 +1163,7 @@ impl<'run, 'src> Parser<'run, 'src> {
11521163
self.expect(ParenR)?;
11531164
}
11541165

1155-
let attribute = Attribute::new(name, arguments)?;
1166+
let attribute = Attribute::new(name, arguments, inverted)?;
11561167

11571168
let first = attributes.get(&attribute).or_else(|| {
11581169
if attribute.repeatable() {
@@ -2668,6 +2679,17 @@ mod tests {
26682679
kind: UnknownAttribute { attribute: "unknown" },
26692680
}
26702681

2682+
error! {
2683+
name: invalid_invertable_attribute,
2684+
input: "[not(private)]\nsome_recipe:\n @exit 3",
2685+
offset: 5,
2686+
line: 0,
2687+
column: 5,
2688+
width: 7,
2689+
kind: InvalidInvertedAttribute { attr_name: "private" },
2690+
2691+
}
2692+
26712693
error! {
26722694
name: set_unknown,
26732695
input: "set shall := []",

src/recipe.rs

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -116,19 +116,81 @@ impl<'src, D> Recipe<'src, D> {
116116
}
117117

118118
pub(crate) fn enabled(&self) -> bool {
119-
let linux = self.attributes.contains(AttributeDiscriminant::Linux);
120-
let macos = self.attributes.contains(AttributeDiscriminant::Macos);
121-
let openbsd = self.attributes.contains(AttributeDiscriminant::Openbsd);
122-
let unix = self.attributes.contains(AttributeDiscriminant::Unix);
123-
let windows = self.attributes.contains(AttributeDiscriminant::Windows);
124-
125-
(!windows && !linux && !macos && !openbsd && !unix)
126-
|| (cfg!(target_os = "linux") && (linux || unix))
127-
|| (cfg!(target_os = "macos") && (macos || unix))
128-
|| (cfg!(target_os = "openbsd") && (openbsd || unix))
129-
|| (cfg!(target_os = "windows") && windows)
130-
|| (cfg!(unix) && unix)
131-
|| (cfg!(windows) && windows)
119+
use attribute_set::InvertedStatus;
120+
121+
struct Systems {
122+
linux: bool,
123+
macos: bool,
124+
openbsd: bool,
125+
unix: bool,
126+
windows: bool,
127+
}
128+
129+
let linux = self
130+
.attributes
131+
.contains_invertible(AttributeDiscriminant::Linux);
132+
let macos = self
133+
.attributes
134+
.contains_invertible(AttributeDiscriminant::Macos);
135+
let openbsd = self
136+
.attributes
137+
.contains_invertible(AttributeDiscriminant::Openbsd);
138+
let unix = self
139+
.attributes
140+
.contains_invertible(AttributeDiscriminant::Unix);
141+
let windows = self
142+
.attributes
143+
.contains_invertible(AttributeDiscriminant::Windows);
144+
145+
if [linux, macos, openbsd, unix, windows]
146+
.into_iter()
147+
.all(|x| x.is_none())
148+
{
149+
return true;
150+
}
151+
152+
let systems = Systems {
153+
linux: matches!(linux, Some(InvertedStatus::Normal)),
154+
macos: matches!(macos, Some(InvertedStatus::Normal)),
155+
openbsd: matches!(openbsd, Some(InvertedStatus::Normal)),
156+
unix: matches!(unix, Some(InvertedStatus::Normal)),
157+
windows: matches!(windows, Some(InvertedStatus::Normal)),
158+
};
159+
160+
let disabled = Systems {
161+
linux: matches!(linux, Some(InvertedStatus::Inverted)),
162+
macos: matches!(macos, Some(InvertedStatus::Inverted)),
163+
openbsd: matches!(openbsd, Some(InvertedStatus::Inverted)),
164+
unix: matches!(unix, Some(InvertedStatus::Inverted)),
165+
windows: matches!(windows, Some(InvertedStatus::Inverted)),
166+
};
167+
168+
if cfg!(target_os = "linux") {
169+
return !(disabled.linux || disabled.unix)
170+
&& ((systems.linux || systems.unix)
171+
|| (!systems.openbsd && !systems.windows && !systems.macos));
172+
}
173+
174+
if cfg!(target_os = "openbsd") {
175+
return !disabled.openbsd
176+
&& (systems.openbsd || (!systems.windows && !systems.macos && !systems.linux));
177+
}
178+
179+
if cfg!(target_os = "windows") || cfg!(windows) {
180+
return !disabled.windows
181+
&& (systems.windows
182+
|| (!systems.openbsd && !systems.unix && !systems.macos && !systems.linux));
183+
}
184+
185+
if cfg!(target_os = "macos") {
186+
return !disabled.macos
187+
&& (systems.macos || (!systems.openbsd && !systems.windows && !systems.linux));
188+
}
189+
190+
if cfg!(unix) {
191+
return !(disabled.unix) && (systems.unix || !systems.windows);
192+
}
193+
false
132194
}
133195

134196
fn print_exit_message(&self) -> bool {

tests/attributes.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,27 @@ fn duplicate_non_repeatable_attributes_are_forbidden() {
254254
.status(EXIT_FAILURE)
255255
.run();
256256
}
257+
258+
#[test]
259+
fn invertible_attributes() {
260+
let test = Test::new().justfile(
261+
"
262+
[not(windows)]
263+
non-windows-recipe:
264+
echo 'non-windows'
265+
266+
[windows]
267+
windows-recipe:
268+
echo 'windows'
269+
",
270+
);
271+
272+
#[cfg(windows)]
273+
test.stdout("windows\n").stderr("echo 'windows'\n").run();
274+
275+
#[cfg(not(windows))]
276+
test
277+
.stdout("non-windows\n")
278+
.stderr("echo 'non-windows'\n")
279+
.run();
280+
}

0 commit comments

Comments
 (0)