Skip to main content

yash_builtin/pwd/
syntax.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Command line argument parser for the pwd built-in
18
19use super::Mode;
20use crate::common::syntax::OptionOccurrence;
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::pretty::Report;
27use yash_env::source::pretty::ReportType;
28use yash_env::source::pretty::Snippet;
29use yash_env::source::pretty::Span;
30use yash_env::source::pretty::SpanRole;
31use yash_env::source::pretty::add_span;
32
33/// Error in parsing command line arguments
34#[derive(Clone, Debug, Eq, Error, PartialEq)]
35#[non_exhaustive]
36pub enum Error {
37    /// An error occurred in the common parser.
38    #[error(transparent)]
39    CommonError(#[from] crate::common::syntax::ParseError<'static>),
40
41    /// One or more operands are given.
42    #[error("unexpected operand")]
43    UnexpectedOperands(Vec<Field>),
44}
45
46impl Error {
47    /// Converts this error to a [`Report`].
48    #[must_use]
49    pub fn to_report(&self) -> Report<'_> {
50        match self {
51            Self::CommonError(e) => e.to_report(),
52
53            Self::UnexpectedOperands(operands) => {
54                let mut report = Report::new();
55                report.r#type = ReportType::Error;
56                report.title = "unexpected operand".into();
57                report.snippets = Snippet::with_primary_span(
58                    &operands[0].origin,
59                    format!("{}: unexpected", operands[0]).into(),
60                );
61                for operand in &operands[1..] {
62                    add_span(
63                        &operand.origin.code,
64                        Span {
65                            range: operand.origin.byte_range(),
66                            role: SpanRole::Primary {
67                                label: format!("{}: unexpected", operand).into(),
68                            },
69                        },
70                        &mut report.snippets,
71                    );
72                }
73                report
74            }
75        }
76    }
77}
78
79impl<'a> From<&'a Error> for Report<'a> {
80    #[inline]
81    fn from(error: &'a Error) -> Self {
82        error.to_report()
83    }
84}
85
86/// Result of parsing command line arguments
87pub type Result = std::result::Result<Mode, Error>;
88
89const OPTION_SPECS: &[OptionSpec] = &[
90    OptionSpec::new().short('L').long("logical"),
91    OptionSpec::new().short('P').long("physical"),
92];
93
94fn mode_for_option(option: &OptionOccurrence) -> Mode {
95    match option.spec.get_short() {
96        Some('L') => Mode::Logical,
97        Some('P') => Mode::Physical,
98        _ => unreachable!(),
99    }
100}
101
102/// Parses command line arguments for the pwd built-in.
103pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result {
104    let parser_mode = crate::common::syntax::Mode::with_env(env);
105    let (options, operands) = parse_arguments(OPTION_SPECS, parser_mode, args)?;
106
107    if !operands.is_empty() {
108        return Err(Error::UnexpectedOperands(operands));
109    }
110
111    Ok(options.last().map(mode_for_option).unwrap_or_default())
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn no_arguments() {
120        let env = Env::new_virtual();
121        let result = parse(&env, vec![]);
122        assert_eq!(result, Ok(Mode::Logical));
123    }
124
125    #[test]
126    fn logical_option() {
127        let env = Env::new_virtual();
128        let result = parse(&env, Field::dummies(["-L"]));
129        assert_eq!(result, Ok(Mode::Logical));
130    }
131
132    #[test]
133    fn physical_option() {
134        let env = Env::new_virtual();
135        let result = parse(&env, Field::dummies(["-P"]));
136        assert_eq!(result, Ok(Mode::Physical));
137    }
138
139    #[test]
140    fn last_option_wins() {
141        let env = Env::new_virtual();
142
143        let result = parse(&env, Field::dummies(["-L", "-P"]));
144        assert_eq!(result, Ok(Mode::Physical));
145
146        let result = parse(&env, Field::dummies(["-P", "-L"]));
147        assert_eq!(result, Ok(Mode::Logical));
148
149        let result = parse(&env, Field::dummies(["-LPL"]));
150        assert_eq!(result, Ok(Mode::Logical));
151
152        let result = parse(&env, Field::dummies(["-PLP"]));
153        assert_eq!(result, Ok(Mode::Physical));
154    }
155
156    #[test]
157    fn unexpected_operand() {
158        let env = Env::new_virtual();
159        let args = Field::dummies(["foo"]);
160        let result = parse(&env, args.clone());
161        assert_eq!(result, Err(Error::UnexpectedOperands(args)));
162    }
163
164    #[test]
165    fn unexpected_operands_after_options() {
166        let env = Env::new_virtual();
167        let args = Field::dummies(["-LP", "-L", "--", "one", "two"]);
168        let operands = args[3..].to_vec();
169        let result = parse(&env, args);
170        assert_eq!(result, Err(Error::UnexpectedOperands(operands)));
171    }
172}