yash_builtin/unalias/
syntax.rs1use super::Command;
20use crate::common::syntax::Mode;
21use crate::common::syntax::OptionSpec;
22use crate::common::syntax::parse_arguments;
23use thiserror::Error;
24use yash_env::Env;
25use yash_env::semantics::Field;
26use yash_env::source::Location;
27use yash_env::source::pretty::{Report, ReportType, Snippet, Span, SpanRole, add_span};
28
29pub const OPTION_SPECS: &[OptionSpec] = &[OptionSpec::new().short('a')];
31
32#[derive(Clone, Debug, Eq, Error, PartialEq)]
34pub enum Error {
35 #[error(transparent)]
37 CommonError(#[from] crate::common::syntax::ParseError<'static>),
38
39 #[error("`-a` cannot be used with operands")]
41 ConflictingOptionAndOperand {
42 option_location: Location,
43 operand_location: Location,
44 },
45
46 #[error("no option or operand specified")]
48 MissingArgument,
49}
50
51impl Error {
52 #[must_use]
54 pub fn to_report(&self) -> Report<'_> {
55 let (title, snippets) = match self {
56 Self::CommonError(e) => return e.to_report(),
57
58 Self::ConflictingOptionAndOperand {
59 option_location,
60 operand_location,
61 } => ("`-a` cannot be used with operands", {
62 let mut snippets =
63 Snippet::with_primary_span(option_location, "`-a` specified here".into());
64 add_span(
65 &operand_location.code,
66 Span {
67 range: operand_location.byte_range(),
68 role: SpanRole::Primary {
69 label: "operand specified here".into(),
70 },
71 },
72 &mut snippets,
73 );
74 snippets
75 }),
76
77 Self::MissingArgument => ("no option or operand specified", vec![]),
78 };
79
80 let mut report = Report::new();
81 report.r#type = ReportType::Error;
82 report.title = title.into();
83 report.snippets = snippets;
84 report
85 }
86}
87
88impl<'a> From<&'a Error> for Report<'a> {
89 #[inline]
90 fn from(error: &'a Error) -> Self {
91 error.to_report()
92 }
93}
94
95pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result<Command, Error> {
97 let mode = Mode::with_env(env);
98 let (mut options, operands) = parse_arguments(OPTION_SPECS, mode, args)?;
99
100 for option in &options {
101 debug_assert_eq!(option.spec.get_short(), Some('a'));
102 }
103
104 match (options.pop(), operands.is_empty()) {
105 (None, true) => Err(Error::MissingArgument),
106 (None, false) => Ok(Command::Remove(operands)),
107 (Some(_), true) => Ok(Command::RemoveAll),
108 (Some(option), false) => Err(Error::ConflictingOptionAndOperand {
109 option_location: option.location,
110 operand_location: { operands }.swap_remove(0).origin,
111 }),
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn all_option() {
121 let env = Env::new_virtual();
122 let result = parse(&env, Field::dummies(["-a"]));
123 assert_eq!(result, Ok(Command::RemoveAll));
124 }
125
126 #[test]
127 fn operands() {
128 let env = Env::new_virtual();
129 let operands = Field::dummies(["foo", "bar"]);
130 let result = parse(&env, operands.clone());
131 assert_eq!(result, Ok(Command::Remove(operands)));
132 }
133
134 #[test]
135 fn missing_arguments() {
136 let env = Env::new_virtual();
137 let result = parse(&env, vec![]);
138 assert_eq!(result, Err(Error::MissingArgument));
139 }
140
141 #[test]
142 fn option_and_operand() {
143 let env = Env::new_virtual();
144 let args = Field::dummies(["-a", "foo"]);
145 let result = parse(&env, args);
146 assert_eq!(
147 result,
148 Err(Error::ConflictingOptionAndOperand {
149 option_location: Location::dummy("-a"),
150 operand_location: Location::dummy("foo"),
151 }),
152 );
153 }
154}