diff --git a/yash-builtin/src/command.rs b/yash-builtin/src/command.rs index a243b274..12af60ec 100644 --- a/yash-builtin/src/command.rs +++ b/yash-builtin/src/command.rs @@ -106,6 +106,11 @@ //! //! The `-p` option depends on [`System::confstr_path`] to obtain the standard //! search path. See [`RealSystem::confstr_path`] for the supported platforms. +//! +//! The [`type`] built-in is equivalent to the `command` built-in with the `-V` +//! option. +//! +//! [`type`]: crate::type use crate::common::report_error; use enumset::EnumSet; diff --git a/yash-builtin/src/command/syntax.rs b/yash-builtin/src/command/syntax.rs index 7ce110c0..2409e71c 100644 --- a/yash-builtin/src/command/syntax.rs +++ b/yash-builtin/src/command/syntax.rs @@ -22,6 +22,7 @@ use super::Invoke; use super::Search; use crate::common::syntax::parse_arguments; use crate::common::syntax::Mode; +use crate::common::syntax::OptionOccurrence; use crate::common::syntax::OptionSpec; use crate::common::syntax::ParseError; use thiserror::Error; @@ -55,9 +56,13 @@ const OPTION_SPECS: &[OptionSpec] = &[ OptionSpec::new().short('V').long("verbose-identify"), ]; -pub fn parse(env: &Env, args: Vec) -> Result { - let (options, operands) = parse_arguments(OPTION_SPECS, Mode::with_env(env), args)?; - +/// Interprets the parsed command line arguments +/// +/// This function converts the result of [`parse_arguments`] into a `Command`. +pub fn interpret( + options: Vec>, + operands: Vec, +) -> Result { // Interpret options let mut standard_path = false; let mut verbose_identify = None; @@ -89,6 +94,12 @@ pub fn parse(env: &Env, args: Vec) -> Result { } } +/// Parses command line arguments of the `command` built-in +pub fn parse(env: &Env, args: Vec) -> Result { + let (options, operands) = parse_arguments(OPTION_SPECS, Mode::with_env(env), args)?; + interpret(options, operands) +} + #[cfg(test)] mod tests { use super::*; diff --git a/yash-builtin/src/lib.rs b/yash-builtin/src/lib.rs index 377f1211..bec64052 100644 --- a/yash-builtin/src/lib.rs +++ b/yash-builtin/src/lib.rs @@ -76,6 +76,8 @@ pub mod shift; #[cfg(feature = "yash-semantics")] pub mod source; pub mod trap; +#[cfg(feature = "yash-semantics")] +pub mod r#type; pub mod typeset; pub mod unalias; pub mod unset; @@ -270,6 +272,14 @@ pub const BUILTINS: &[(&str, Builtin)] = &[ execute: |env, args| Box::pin(trap::main(env, args)), }, ), + #[cfg(feature = "yash-semantics")] + ( + "type", + Builtin { + r#type: Mandatory, + execute: |env, args| Box::pin(r#type::main(env, args)), + }, + ), ( "typeset", Builtin { diff --git a/yash-builtin/src/type.rs b/yash-builtin/src/type.rs new file mode 100644 index 00000000..4cfd4577 --- /dev/null +++ b/yash-builtin/src/type.rs @@ -0,0 +1,104 @@ +// This file is part of yash, an extended POSIX shell. +// Copyright (C) 2024 WATANABE Yuki +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Type built-in +//! +//! The **`type`** built-in identifies the type of commands. +//! +//! # Synopsis +//! +//! ```sh +//! type [nameā€¦] +//! ``` +//! +//! # Description +//! +//! The `type` built-in prints the description of the specified command names. +//! +//! # Options +//! +//! (TODO: Non-standard options are not supported yet.) +//! +//! # Operands +//! +//! The ***name*** operands specify the command names to identify. +//! +//! # Standard output +//! +//! The command descriptions are printed to the standard output. +//! +//! # Errors +//! +//! It is an error if the *name* is not found. +//! +//! # Exit status +//! +//! The exit status is zero if all the *name*s are found, and non-zero +//! otherwise. +//! +//! # Portability +//! +//! POSIX requires that the *name* operand be specified, but many +//! implementations allow it to be omitted, in which case the built-in does +//! nothing. +//! +//! The format of the output is unspecified by POSIX. In this implementation, +//! the `type` built-in is equivalent to the [`command`] built-in with the `-V` +//! option. +//! +//! [`command`]: crate::command + +use crate::command::syntax::interpret; +use crate::command::Command; +use crate::common::report_error; +use crate::common::syntax::parse_arguments; +use crate::common::syntax::Mode; +use crate::common::syntax::OptionOccurrence; +use crate::common::syntax::OptionSpec; +use yash_env::semantics::Field; +use yash_env::Env; +use yash_syntax::source::Location; + +const OPTION_SPECS: &[OptionSpec] = &[ + // TODO: Non-standard options +]; + +fn parse(env: &mut Env, args: Vec) -> Result { + let (mut options, operands) = parse_arguments(OPTION_SPECS, Mode::with_env(env), args)?; + + // `type` is equivalent to `command -V`, so add the `-V` option and delegate + // to the `command` built-in. + let spec = OptionSpec::new().short('V').long("verbose-identify"); + let location = env.stack.current_builtin().map_or_else( + || Location::dummy(""), + |builtin| builtin.name.origin.clone(), + ); + options.push(OptionOccurrence { + spec: &spec, + location, + argument: None, + }); + + interpret(options, operands) +} + +/// Entry point of the `type` built-in +pub async fn main(env: &mut Env, args: Vec) -> crate::Result { + match parse(env, args) { + Ok(command) => command.execute(env).await, + Err(error) => report_error(env, &error).await, + } +}