From 84bfea7add93a98f83ad958151cca718c33bc0a4 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 11 Jan 2019 02:31:29 +0000 Subject: Add the stdio backend This patch adds the stdio backend which acts as a fallback backend and uses standard input and output. For password queries, the rpassword crate is used to suppress output. Also, default_backend is changed to return Stdio if Dialog is not available. --- CHANGELOG.md | 1 + Cargo.toml | 1 + examples/backend-stdio.rs | 13 +++++++ src/backends/dialog.rs | 4 +++ src/backends/mod.rs | 3 ++ src/backends/stdio.rs | 89 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 12 +++++-- 7 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 examples/backend-stdio.rs create mode 100644 src/backends/stdio.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b11008c..ff1b4a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - Refactor `default_backend` to return a `Box`. - Check the `DIALOG` and `DISPLAY` environment variables in `default_backend`. +- Add the `Stdio` backend. # v0.1.1 (2019-01-11) - Add the `Password` dialog box. diff --git a/Cargo.toml b/Cargo.toml index 1a6c816..a4087a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ readme = "README.md" license = "MIT" [dependencies] +rpassword = "2" diff --git a/examples/backend-stdio.rs b/examples/backend-stdio.rs new file mode 100644 index 0000000..6dbeb8d --- /dev/null +++ b/examples/backend-stdio.rs @@ -0,0 +1,13 @@ +// Copyright (C) 2019 Robin Krahl +// SPDX-License-Identifier: MIT + +use dialog::backends; +use dialog::DialogBox; + +fn main() -> dialog::Result<()> { + let backend = backends::Stdio::new(); + + dialog::Message::new("This is a message.") + .title("And this is a title:") + .show_with(&backend) +} diff --git a/src/backends/dialog.rs b/src/backends/dialog.rs index 6f078e6..e681caf 100644 --- a/src/backends/dialog.rs +++ b/src/backends/dialog.rs @@ -49,6 +49,10 @@ impl Dialog { self.width = width.to_string(); } + pub(crate) fn is_available() -> bool { + super::is_available("dialog") + } + fn execute( &self, args: Vec<&str>, diff --git a/src/backends/mod.rs b/src/backends/mod.rs index f5af7ef..5ae3cef 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: MIT mod dialog; +mod stdio; mod zenity; pub use crate::backends::dialog::Dialog; +pub use crate::backends::stdio::Stdio; pub use crate::backends::zenity::Zenity; use std::env; @@ -49,6 +51,7 @@ pub(crate) fn is_available(name: &str) -> bool { pub(crate) fn from_str(s: &str) -> Option> { match s.to_lowercase().as_ref() { "dialog" => Some(Box::new(Dialog::new())), + "stdio" => Some(Box::new(Stdio::new())), "zenity" => Some(Box::new(Zenity::new())), _ => None, } diff --git a/src/backends/stdio.rs b/src/backends/stdio.rs new file mode 100644 index 0000000..6838ef1 --- /dev/null +++ b/src/backends/stdio.rs @@ -0,0 +1,89 @@ +// Copyright (C) 2019 Robin Krahl +// SPDX-License-Identifier: MIT + +use std::io::{self, Write}; + +use crate::{Choice, Input, Message, Password, Question, Result}; + +/// The fallback backend using standard input and output. +/// +/// This backend is intended as a fallback backend to use if no other backend is available. The +/// dialogs are printed to the standard output and user input is read from the standard input. +#[derive(Debug)] +pub struct Stdio {} + +impl Stdio { + /// Creates a new `Stdio` instance. + pub fn new() -> Stdio { + Stdio {} + } +} + +impl AsRef for Stdio { + fn as_ref(&self) -> &Self { + self + } +} + +fn print_title(title: &Option) { + if let Some(ref title) = title { + println!("{}", title); + println!("{}", "=".repeat(title.len())); + } +} + +fn read_input() -> Result { + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + Ok(input.trim_end_matches("\n").to_string()) +} + +fn parse_choice(input: &str) -> Choice { + match input.to_lowercase().as_ref() { + "y" => Choice::Yes, + "yes" => Choice::Yes, + "n" => Choice::No, + "no" => Choice::No, + _ => Choice::Cancel, + } +} + +impl super::Backend for Stdio { + fn show_input(&self, input: &Input) -> Result> { + print_title(&input.title); + if let Some(ref default) = input.default { + print!("{} [default: {}]: ", input.text, default); + } else { + print!("{}: ", input.text); + } + io::stdout().flush()?; + + let user_input = read_input()?; + if user_input.is_empty() { + if let Some(ref default) = input.default { + return Ok(Some(default.to_string())); + } + } + Ok(Some(user_input)) + } + + fn show_message(&self, message: &Message) -> Result<()> { + print_title(&message.title); + println!("{}", message.text); + Ok(()) + } + + fn show_password(&self, password: &Password) -> Result> { + print_title(&password.title); + print!("{}: ", password.text); + io::stdout().flush()?; + Ok(Some(rpassword::read_password()?)) + } + + fn show_question(&self, question: &Question) -> Result { + print_title(&question.title); + print!("{} [y/n]: ", question.text); + io::stdout().flush()?; + Ok(parse_choice(&read_input()?)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 80cabb5..da89e2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,8 @@ //! These dialog boxes can be displayed using various backends: //! - [`Dialog`][]: uses `dialog` to display ncurses-based dialog boxes (requires the external //! `dialog` tool) +//! - [`Stdio`][]: prints messages to the standard output and reads user input form standard input +//! (intended as a fallback backend) //! - [`Zenity`][]: uses `zenity` to display GTK-based dialog boxes (requires the external `zenity` //! tool) //! @@ -344,9 +346,11 @@ impl DialogBox for Question { /// - If the `DISPLAY` environment variable is set, the first available backend from this list is /// used: /// - [`Zenity`][] -/// - Otherwise, a [`Dialog`][] instance is returned. +/// - If the [`Dialog`][] backend is available, it is used. +/// - Otherwise, a [`Stdio`][] instance is returned. /// /// [`Dialog`]: backends/struct.Dialog.html +/// [`Stdio`]: backends/struct.Stdio.html /// [`Zenity`]: backends/struct.Zenity.html pub fn default_backend() -> Box { if let Ok(backend) = env::var("DIALOG") { @@ -363,5 +367,9 @@ pub fn default_backend() -> Box { } } - Box::new(backends::Dialog::new()) + if backends::Dialog::is_available() { + Box::new(backends::Dialog::new()) + } else { + Box::new(backends::Stdio::new()) + } } -- cgit v1.2.1