bash_builtins/lib.rs
1//! This crate provides utilities to implement loadable builtins for bash. It
2//! reuses functions provided by bash as much as possible in order to keep
3//! compatibility with existing builtins.
4//!
5//! # What are Loadable Builtins
6//!
7//! Bash, like most shells, has [*builtins*]. A builtin looks like a regular
8//! command, but it is executed in the shell process. Some builtins are used to
9//! interact with the shell (like `cd` or `jobs`), and others are common
10//! utilities (like `printf` or `test`).
11//!
12//! New builtins can be created in a running shell as [*loadable builtins*],
13//! using code from a dynamic library (for example, a `.so` file in Linux). This
14//! is done with the [`enable -f`] command.
15//!
16//! For example, if the crate name is `foo`, and it defines a `bar` builtin,
17//! the following commands will load it:
18//!
19//! ```notrust
20//! $ cargo build --release
21//!
22//! $ enable -f target/release/libfoo.so bar
23//! ```
24//!
25//! [*loadable builtins*]: https://git.savannah.gnu.org/cgit/bash.git/tree/examples/loadables/README?h=bash-5.1
26//! [*builtins*]: https://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html
27//! [`enable -f`]: https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-enable
28//!
29//! # Usage
30//!
31//! ## Crate Configuration
32//!
33//! The crate where the builtin is implemented has to include `cdylib` in its
34//! [`crate-type` field]. This is required to build a dynamic library.
35//!
36//! `Cargo.toml` should contain something similar to this:
37//!
38//! ```notrust
39//! [dependencies]
40#![doc = concat!(
41 env!("CARGO_PKG_NAME"),
42 " = \"",
43 env!("CARGO_PKG_VERSION"), "\"")
44]
45//!
46//! [lib]
47//! crate-type = [ "cdylib" ]
48//! ```
49//!
50//! [`crate-type` field]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field
51//!
52//! ## Main Items
53//!
54//! These are the main items to implement a builtin:
55//!
56//! * The [`builtin_metadata!()`] macro, to generate functions and declarations
57//! required by bash.
58//!
59//! * The [`BuiltinOptions`] derive macro, to generate an option parser.
60//!
61//! * The [`Builtin`] trait, to provide the builtin functionality.
62//!
63//! * The [`Args`] type, to access to the command-line arguments.
64//!
65//! A single crate can contain multiple builtins. Each builtin requires its own
66//! call to [`builtin_metadata!()`].
67//!
68//! ## Basic Structure
69//!
70//! ```
71//! use bash_builtins::{builtin_metadata, Args, Builtin, BuiltinOptions, Result};
72//!
73//! builtin_metadata!(
74//! # name = "SomeName", create = SomeName::default,
75//! // Builtin metadata.
76//! );
77//!
78//! # #[derive(Default)]
79//! struct SomeName {
80//! // Fields to store state.
81//! }
82//!
83//! #[derive(BuiltinOptions)]
84//! enum Opt {
85//! // Options from the command-line arguments.
86//! }
87//!
88//! impl Builtin for SomeName {
89//! fn call(&mut self, args: &mut Args) -> Result<()> {
90//! // builtin implementation
91//! Ok(())
92//! }
93//! }
94//! ```
95//!
96//! # Example
97//!
98//! The following example is a simple counter.
99//!
100//! It accepts some options to modify the stored value.
101//!
102#![doc = concat!("```\n", include_str!("../examples/counter.rs"), "```")]
103//!
104//! This example is available in the `examples/counter.rs` file of the Git
105//! repository of this crate.
106//!
107//! It can be tested with the following commands:
108//!
109//! ```notrust
110//! $ cargo build --release --examples
111//!
112//! $ enable -f target/release/examples/libcounter.so counter
113//!
114//! $ counter
115//! 0
116//!
117//! $ counter
118//! 1
119//!
120//! $ help counter
121//! counter: counter [-r] [-s value] [-a value]
122//! Print a value, and increment it.
123//!
124//! Options:
125//! -r Reset the value to 0.
126//! -s Set the counter to a specific value.
127//! -a Increment the counter by a value.
128//!
129//! $ counter -s -100
130//!
131//! $ counter
132//! -100
133//!
134//! $ counter abcd
135//! bash: counter: too many arguments
136//!
137//! $ enable -d counter
138//!
139//! $ counter
140//! bash: counter: command not found
141//! ```
142//!
143//! # Builtin Documentation
144//!
145//! A bash builtin has two fields for the documentation:
146//!
147//! * [`short_doc`]: a single line of text to describe how to use the builtin.
148//! * [`long_doc`]: a detailed explanation of the builtin.
149//!
150//! [`short_doc`]: bash_builtins_macro::builtin_metadata!()#short_doc-optional
151//! [`long_doc`]: bash_builtins_macro::builtin_metadata!()#long_doc-optional
152//!
153//! Both fields are optional, but it is recommend to include them.
154//!
155//! See the documentation of the [`builtin_metadata!()`] macro for more details.
156//!
157//! # Builtin Initialization
158//!
159//! When the builtin is loaded, the function given in either [`create`] or
160//! [`try_create`] is executed. This function will create a new instance of a
161//! type that implements the [`Builtin`] trait.
162//!
163//! [`try_create`] is used if the initialization mail fails.
164//!
165//! ## Example of a Fallible Initialization
166//!
167//! ```
168//! # use bash_builtins::*;
169//! use std::fs::File;
170//!
171//! builtin_metadata!(
172//! # name = "x",
173//! // …
174//! try_create = Foo::new,
175//! );
176//!
177//! struct Foo {
178//! file: File
179//! }
180//!
181//! impl Foo {
182//! fn new() -> Result<Foo> {
183//! let file = File::open("/some/config/file")?;
184//! Ok(Foo { file })
185//! }
186//! }
187//!
188//! impl Builtin for Foo {
189//! fn call(&mut self, args: &mut Args) -> Result<()> {
190//! # let _ = args;
191//! // …
192//! Ok(())
193//! }
194//! }
195//! ```
196//!
197//! [`create`]: bash_builtins_macro::builtin_metadata!()#create
198//! [`try_create`]: bash_builtins_macro::builtin_metadata!()#try_create
199//!
200//! # Builtin Removal
201//!
202//! A loadable builtin can be removed from a running shell with `enable -d`.
203//!
204//! If a builtin needs to run any cleanup process when it is unloaded, then it
205//! must implement [`Drop`](std::ops::Drop). The value is dropped just before
206//! the builtin is deleted.
207//!
208//! # Parsing Command Line Options
209//!
210//! Bash builtins use an internal implementation of `getopt()` to parse command
211//! line arguments. The [`BuiltinOptions`] derive macro provides an easy-to-use
212//! method to generate an options parser on top of this `getopt()`.
213//!
214//! See the macro documentation for details on how to use it.
215//!
216//! # Error Handling
217//!
218//! The macros [`error!()`] and [`warning!()`] can be used to produce log
219//! messages to the standard error stream (*stderr*). They use the bash
220//! functions `builtin_error` and `builtin_warning`.
221//!
222//! [Recoverable errors] can be used as the return value of [`Builtin::call`],
223//! usually with the [`?` operator]. In such cases, the message from the error
224//! is printed to *stderr*, and the exit code of the builtin is `1`.
225//!
226//! [Recoverable Errors]: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html
227//! [`?` operator]: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
228//! Use [`Error::ExitCode`] to return a specific exit code. See the [`Error`]
229//! documentation for more details.
230//!
231//! # Using Shell Variables
232//!
233//! The module [`variables`] contains functions to manage the shell variables.
234//!
235//! ## Example
236//!
237//! The following example uses the variable `$SOMENAME_LIMIT` to set the
238//! configuration value for the builtin. If it is not present, or its value is
239//! not a valid `usize`, it uses a default value
240//!
241//! ```
242//! use bash_builtins::variables;
243//!
244//! const DEFAULT_LIMIT: usize = 1024;
245//!
246//! const LIMIT_VAR_NAME: &str = "SOMENAME_LIMIT";
247//!
248//! fn get_limit() -> usize {
249//! variables::find_as_string(LIMIT_VAR_NAME)
250//! .as_ref()
251//! .and_then(|v| v.to_str().ok())
252//! .and_then(|v| v.parse().ok())
253//! .unwrap_or(DEFAULT_LIMIT)
254//! }
255//! ```
256//!
257//! # Creating Dynamic Variables
258//!
259//! Dynamic variables are shell variables that use custom functions each time
260//! they are accessed (like `$SECONDS` or `$RANDOM`).
261//!
262//! Use [`variables::bind`] to create a dynamic variable with any type
263//! implementing [`DynamicVariable`](variables::DynamicVariable).
264//!
265//! # Panic Handling
266//!
267//! Panics are captured with [`panic::catch_unwind`], so they should not reach
268//! the bash process.
269//!
270//! After a panic the builtin is [“poisoned”], and any attempt to use it will
271//! print the error `invalid internal state` on the terminal. Users will have
272//! to remove it (`enable -d`) and enable it again. Also, when a poisoned
273//! builtin is removed, its destructors (if any) are not executed.
274//!
275//! If you want to avoid this behaviour you have to use [`panic::catch_unwind`]
276//! in your own code.
277//!
278//! [“poisoned”]: https://doc.rust-lang.org/stable/std/sync/struct.Mutex.html#poisoning
279//!
280//! It is important to *not* set the [`panic` setting] to `"abort"`. If the
281//! dynamic library is built with this setting, a panic will terminate the bash
282//! process.
283//!
284//! [`panic::catch_unwind`]: std::panic::catch_unwind
285//! [`panic` setting]: https://doc.rust-lang.org/cargo/reference/profiles.html#panic
286//! [`BuiltinOptions`]: bash_builtins_macro::BuiltinOptions
287
288#![cfg_attr(docsrs, feature(doc_cfg))]
289
290mod args;
291mod errors;
292
293pub mod convert;
294pub mod log;
295pub mod variables;
296
297#[doc(hidden)]
298pub mod ffi;
299
300// Re-export macros.
301pub use bash_builtins_macro::{builtin_metadata, BuiltinOptions};
302
303// Re-export public items.
304pub use args::{Args, BuiltinOptions};
305pub use errors::{Error, Result};
306
307/// The `Builtin` trait contains the implementation for a bash builtin.
308pub trait Builtin {
309 /// Method invoked when the builtin is typed in the prompt.
310 ///
311 /// It returns an instance of [`Result`]. The value `Ok(())` returns the
312 /// exit code `0` to the shell. [`Error::ExitCode`] can be used to return a
313 /// specific exit code.
314 ///
315 /// Any error type that implements [`std::error::Error`] can be used with
316 /// the `?` operator to return an error from this method.
317 ///
318 /// Command-line arguments are read from the [`Args`] instance.
319 fn call(&mut self, args: &mut Args) -> Result<()>;
320}