Skip to main content

yash_builtin/source/
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//! Parsing command line arguments to the source built-in
18
19use super::Command;
20use crate::common::report::report_error;
21use crate::common::syntax::Mode;
22use crate::common::syntax::ParseError;
23use crate::common::syntax::parse_arguments;
24use thiserror::Error;
25use yash_env::Env;
26use yash_env::semantics::Field;
27use yash_env::source::pretty::{Report, ReportType};
28use yash_env::system::{Fcntl, Isatty, Write};
29
30/// Error in parsing command line arguments
31#[derive(Clone, Debug, Eq, Error, PartialEq)]
32#[non_exhaustive]
33pub enum Error {
34    /// An error occurred in the common parser.
35    #[error(transparent)]
36    CommonError(#[from] ParseError<'static>),
37
38    /// The file to be executed is not specified.
39    #[error("missing file operand")]
40    MissingFile,
41}
42
43impl Error {
44    /// Converts this error to a report.
45    #[must_use]
46    pub fn to_report(&self) -> Report<'_> {
47        match self {
48            Self::CommonError(e) => e.to_report(),
49            Self::MissingFile => {
50                let mut report = Report::new();
51                report.r#type = ReportType::Error;
52                report.title = "missing file operand".into();
53                report
54            }
55        }
56    }
57
58    /// Reports the error to the standard error.
59    #[inline(always)]
60    pub async fn report<S>(&self, env: &mut Env<S>) -> crate::Result
61    where
62        S: Fcntl + Isatty + Write,
63    {
64        report_error(env, self).await
65    }
66}
67
68impl<'a> From<&'a Error> for Report<'a> {
69    #[inline]
70    fn from(error: &'a Error) -> Self {
71        error.to_report()
72    }
73}
74
75/// Parses command line arguments to the source built-in.
76pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result<Command, Error> {
77    let mode = Mode::with_env(env);
78    let (_options, mut operands) = parse_arguments(&[], mode, args)?;
79    if operands.is_empty() {
80        return Err(Error::MissingFile);
81    }
82    let file = operands.remove(0);
83    let params = operands;
84    Ok(Command { file, params })
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn file_only() {
93        let env = Env::new_virtual();
94        let args = vec![Field::dummy("foo")];
95        assert_eq!(
96            parse(&env, args),
97            Ok(Command {
98                file: Field::dummy("foo"),
99                params: vec![],
100            })
101        );
102    }
103
104    #[test]
105    fn file_and_parameters() {
106        let env = Env::new_virtual();
107        let args = Field::dummies(["my/file", "foo", "bar"]);
108        assert_eq!(
109            parse(&env, args),
110            Ok(Command {
111                file: Field::dummy("my/file"),
112                params: Field::dummies(["foo", "bar"]),
113            })
114        );
115    }
116
117    #[test]
118    fn no_file() {
119        let env = Env::new_virtual();
120        let args = vec![];
121        assert_eq!(parse(&env, args), Err(Error::MissingFile));
122    }
123
124    #[test]
125    fn unknown_short_option() {
126        let env = Env::new_virtual();
127        let args = Field::dummies(["-@", "foo"]);
128        assert_eq!(
129            parse(&env, args),
130            Err(Error::CommonError(ParseError::UnknownShortOption(
131                '@',
132                Field::dummy("-@"),
133            ))),
134        );
135    }
136}