Skip to main content

yash_builtin/
alias.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 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//! Alias built-in.
18//!
19//! This module implements the [`alias` built-in], which defines aliases or prints
20//! alias definitions.
21//!
22//! [`alias` built-in]: https://magicant.github.io/yash-rs/builtins/alias.html
23
24use crate::common::output;
25use crate::common::report::merge_reports;
26use crate::common::report::report_error;
27use crate::common::report::report_failure;
28use crate::common::syntax::Mode;
29use crate::common::syntax::parse_arguments;
30use yash_env::Env;
31use yash_env::builtin::Result;
32use yash_env::semantics::Field;
33use yash_env::system::{Fcntl, Isatty, Write};
34
35/// Parsed command line arguments
36#[derive(Clone, Debug, Eq, PartialEq)]
37#[non_exhaustive]
38pub struct Command {
39    /// Operands to the alias built-in
40    pub operands: Vec<Field>,
41}
42
43pub mod semantics;
44
45/// Entry point for executing the `alias` built-in
46pub async fn main<S>(env: &mut Env<S>, args: Vec<Field>) -> Result
47where
48    S: Isatty + Fcntl + Write,
49{
50    let mode = Mode::with_env(env);
51    // TODO support options
52    match parse_arguments(&[], mode, args) {
53        Ok((_options, operands)) => {
54            let command = Command { operands };
55            let (result, errors) = command.execute(env).await;
56            let mut result = output(env, &result).await;
57            if let Some(report) = merge_reports(&errors) {
58                result = result.max(report_failure(env, report).await);
59            }
60            result
61        }
62
63        Err(e) => report_error(env, &e).await,
64    }
65}
66
67#[allow(clippy::bool_assert_comparison)]
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use futures_util::FutureExt as _;
72    use yash_env::semantics::ExitStatus;
73    use yash_env::source::Source;
74
75    #[test]
76    fn builtin_defines_alias() {
77        let mut env = Env::new_virtual();
78        let args = Field::dummies(["foo=bar baz"]);
79
80        let result = main(&mut env, args).now_or_never().unwrap();
81        assert_eq!(result, Result::new(ExitStatus::SUCCESS));
82
83        assert_eq!(env.aliases.len(), 1);
84
85        let alias = env.aliases.get("foo").unwrap().0.as_ref();
86        assert_eq!(alias.name, "foo");
87        assert_eq!(alias.replacement, "bar baz");
88        assert_eq!(alias.global, false);
89        assert_eq!(*alias.origin.code.value.borrow(), "foo=bar baz");
90        assert_eq!(alias.origin.code.start_line_number.get(), 1);
91        assert_eq!(*alias.origin.code.source, Source::Unknown);
92        assert_eq!(alias.origin.range, 0..11);
93    }
94
95    #[test]
96    fn builtin_defines_many_aliases() {
97        let mut env = Env::new_virtual();
98        let args = Field::dummies(["abc=xyz", "yes=no", "ls=ls --color"]);
99
100        let result = main(&mut env, args).now_or_never().unwrap();
101        assert_eq!(result, Result::new(ExitStatus::SUCCESS));
102
103        assert_eq!(env.aliases.len(), 3);
104
105        let abc = env.aliases.get("abc").unwrap().0.as_ref();
106        assert_eq!(abc.name, "abc");
107        assert_eq!(abc.replacement, "xyz");
108        assert_eq!(abc.global, false);
109        assert_eq!(*abc.origin.code.value.borrow(), "abc=xyz");
110        assert_eq!(abc.origin.code.start_line_number.get(), 1);
111        assert_eq!(*abc.origin.code.source, Source::Unknown);
112        assert_eq!(abc.origin.range, 0..7);
113
114        let yes = env.aliases.get("yes").unwrap().0.as_ref();
115        assert_eq!(yes.name, "yes");
116        assert_eq!(yes.replacement, "no");
117        assert_eq!(yes.global, false);
118        assert_eq!(*yes.origin.code.value.borrow(), "yes=no");
119        assert_eq!(yes.origin.code.start_line_number.get(), 1);
120        assert_eq!(*yes.origin.code.source, Source::Unknown);
121        assert_eq!(yes.origin.range, 0..6);
122
123        let ls = env.aliases.get("ls").unwrap().0.as_ref();
124        assert_eq!(ls.name, "ls");
125        assert_eq!(ls.replacement, "ls --color");
126        assert_eq!(ls.global, false);
127        assert_eq!(*ls.origin.code.value.borrow(), "ls=ls --color");
128        assert_eq!(ls.origin.code.start_line_number.get(), 1);
129        assert_eq!(*ls.origin.code.source, Source::Unknown);
130        assert_eq!(ls.origin.range, 0..13);
131    }
132
133    #[test]
134    fn builtin_replaces_alias() {
135        let mut env = Env::new_virtual();
136        let args = Field::dummies(["foo=1"]);
137
138        let result = main(&mut env, args).now_or_never().unwrap();
139        assert_eq!(result, Result::new(ExitStatus::SUCCESS));
140
141        let args = Field::dummies(["foo=2"]);
142
143        let result = main(&mut env, args).now_or_never().unwrap();
144        assert_eq!(result, Result::new(ExitStatus::SUCCESS));
145
146        assert_eq!(env.aliases.len(), 1);
147
148        let alias = env.aliases.get("foo").unwrap().0.as_ref();
149        assert_eq!(alias.name, "foo");
150        assert_eq!(alias.replacement, "2");
151        assert_eq!(alias.global, false);
152        // TODO Test with the global option
153        // assert_eq!(alias.global, true);
154    }
155
156    // TODO test case with global aliases
157}