Skip to content

Commit

Permalink
Export built-in (#319)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicant authored Nov 22, 2023
2 parents 5ea33df + f5dca5d commit c087fb1
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 41 deletions.
121 changes: 121 additions & 0 deletions yash-builtin/src/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// This file is part of yash, an extended POSIX shell.
// Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.

//! Export built-in
//!
//! The **`export`** built-in exports shell variables to the environment.
//!
//! # Synopsis
//!
//! ```sh
//! export [-p] [name[=value]…]
//! ```
//!
//! # Description
//!
//! The export built-in (without the `-p` option) exports each of the specified
//! names to the environment, with optional values. If no names are given, or if
//! the `-p` option is given, the names and values of all exported variables are
//! displayed.
//!
//! # Options
//!
//! The **`-p`** (**`--print`**) option causes the shell to display the names and
//! values of all exported variables in a format that can be reused as input to
//! restore the state of these variables. When used with operands, the option
//! limits the output to the specified variables.
//!
//! (TODO: Other non-portable options)
//!
//! # Operands
//!
//! The operands are names of shell variables to be exported. Each name may
//! optionally be followed by `=` and a *value* to assign to the variable.
//!
//! # Exit status
//!
//! Zero unless an error occurs.
//!
//! # Errors
//!
//! When exporting a variable with a value, it is an error if the variable is
//! read-only.
//!
//! When printing variables, it is an error if an operand names a non-existing
//! variable.
//!
//! # Portability
//!
//! This built-in is part of the POSIX standard. Printing variables is portable
//! only when the `-p` option is used without operands.
//!
//! # Implementation notes
//!
//! The implementation of this built-in depends on that of the
//! [`typeset`](crate::typeset) built-in.
use crate::common::output;
use crate::common::report_error;
use crate::common::report_failure;
use crate::typeset::syntax::interpret;
use crate::typeset::syntax::parse;
use crate::typeset::syntax::OptionSpec;
use crate::typeset::syntax::PRINT_OPTION;
use crate::typeset::to_message;
use crate::typeset::Command;
use crate::typeset::PrintVariablesContext;
use crate::typeset::Scope::Global;
use crate::typeset::VariableAttr::Export;
use yash_env::option::State::On;
use yash_env::semantics::Field;
use yash_env::Env;

/// List of portable options applicable to the export built-in
pub static PORTABLE_OPTIONS: &[OptionSpec<'static>] = &[PRINT_OPTION];

/// Variable printing context for the export built-in
pub const PRINT_VARIABLES_CONTEXT: PrintVariablesContext<'static> = PrintVariablesContext {
builtin_name: "export",
options_allowed: &[],
};

/// Entry point of the export built-in
pub async fn main(env: &mut Env, args: Vec<Field>) -> yash_env::builtin::Result {
match parse(PORTABLE_OPTIONS, args) {
Ok((options, operands)) => match interpret(options, operands) {
Ok(mut command) => {
match &mut command {
Command::SetVariables(sv) => {
sv.attrs.push((Export, On));
sv.scope = Global;
}
Command::PrintVariables(pv) => {
pv.attrs.push((Export, On));
pv.scope = Global;
}
Command::SetFunctions(sf) => unreachable!("{sf:?}"),
Command::PrintFunctions(pf) => unreachable!("{pf:?}"),
}
match command.execute(env, &PRINT_VARIABLES_CONTEXT) {
Ok(result) => output(env, &result).await,
Err(errors) => report_failure(env, to_message(&errors)).await,
}
}
Err(error) => report_error(env, &error).await,
},
Err(error) => report_error(env, &error).await,
}
}
8 changes: 8 additions & 0 deletions yash-builtin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub mod r#continue;
#[cfg(feature = "yash-semantics")]
pub mod exec;
pub mod exit;
pub mod export;
pub mod jobs;
pub mod pwd;
pub mod readonly;
Expand Down Expand Up @@ -118,6 +119,13 @@ pub const BUILTINS: &[(&str, Builtin)] = &[
execute: |env, args| Box::pin(exit::main(env, args)),
},
),
(
"export",
Builtin {
r#type: Special,
execute: |env, args| Box::pin(export::main(env, args)),
},
),
(
"jobs",
Builtin {
Expand Down
47 changes: 41 additions & 6 deletions yash-builtin/src/typeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,15 @@
//!
//! # Implementation notes
//!
//! TBD
//! The implementation of this built-in is also used by the
//! [`export`](crate::export) built-in. Functions that are common to both
//! built-ins are parameterized to support the different behaviors of the
//! built-ins. By customizing the contents of [`Command`] and the
//! [`PrintVariablesContext`] passed to [`Command::execute`], you can even
//! implement a new built-in that behaves differently from both `typeset` and
//! `export`.
use self::syntax::OptionSpec;
use crate::common::{output, report_error, report_failure};
use thiserror::Error;
use yash_env::function::Function;
Expand Down Expand Up @@ -339,6 +346,25 @@ pub struct PrintVariables {
pub scope: Scope,
}

/// Set of information used when printing variables
///
/// [`PrintVariables::execute`] prints a list of commands that invoke a built-in
/// to recreate the variables. This context is used to determine the name of the
/// built-in and the attributes possibly indicated as options to the built-in.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PrintVariablesContext<'a> {
/// Name of the built-in printed as the command name
pub builtin_name: &'a str,
/// Options that may be printed for the built-in
pub options_allowed: &'a [OptionSpec<'a>],
}

/// Variable printing context for the typeset built-in
pub const PRINT_VARIABLES_CONTEXT: PrintVariablesContext<'static> = PrintVariablesContext {
builtin_name: "typeset",
options_allowed: self::syntax::ALL_OPTIONS,
};

/// Attribute that can be set on a function
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
Expand Down Expand Up @@ -423,10 +449,16 @@ impl Command {
/// If there are no errors, the method returns a string that should be
/// printed to the standard output.
/// Otherwise, the method returns a non-empty vector of errors.
pub fn execute(self, env: &mut Env) -> Result<String, Vec<ExecuteError>> {
pub fn execute(
self,
env: &mut Env,
print_variables_context: &PrintVariablesContext,
) -> Result<String, Vec<ExecuteError>> {
match self {
Self::SetVariables(command) => command.execute(env),
Self::PrintVariables(command) => command.execute(&env.variables),
Self::PrintVariables(command) => {
command.execute(&env.variables, print_variables_context)
}
Self::SetFunctions(command) => command.execute(&mut env.functions),
Self::PrintFunctions(command) => command.execute(&env.functions),
}
Expand Down Expand Up @@ -571,8 +603,11 @@ impl std::fmt::Display for ExecuteError {
///
/// The first error's title is used as the message title. The other errors are
/// added as annotations.
///
/// This is a utility for printing errors returned by [`Command::execute`].
/// The returned message can be passed to [`report_failure`].
#[must_use]
fn to_message(errors: &[ExecuteError]) -> Message {
pub fn to_message(errors: &[ExecuteError]) -> Message {
let mut message = Message::from(&errors[0]);
let other_errors = errors[1..].iter().map(ExecuteError::main_annotation);
message.annotations.extend(other_errors);
Expand All @@ -583,7 +618,7 @@ fn to_message(errors: &[ExecuteError]) -> Message {
pub async fn main(env: &mut Env, args: Vec<Field>) -> yash_env::builtin::Result {
match syntax::parse(syntax::ALL_OPTIONS, args) {
Ok((options, operands)) => match syntax::interpret(options, operands) {
Ok(command) => match command.execute(env) {
Ok(command) => match command.execute(env, &PRINT_VARIABLES_CONTEXT) {
Ok(result) => output(env, &result).await,
Err(errors) => report_failure(env, to_message(&errors)).await,
},
Expand Down
Loading

0 comments on commit c087fb1

Please sign in to comment.