aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2019-01-08 03:16:20 +0000
committerRobin Krahl <robin.krahl@ireas.org>2019-01-08 04:54:20 +0100
commitec84c425282c3fd26f5e862b7864ad2ae7ec1e2e (patch)
tree347baad0fc42290ab5da2330d828f284d1ee8d88 /src
parent1c76a540d647f351e27498e6f2135ff404853693 (diff)
downloaddialog-rs-ec84c425282c3fd26f5e862b7864ad2ae7ec1e2e.tar.gz
dialog-rs-ec84c425282c3fd26f5e862b7864ad2ae7ec1e2e.tar.bz2
Add input dialog boxes
This patch implements input dialog boxes. This required some refactoring in the dialog backend to allow additional arguments after the width and the height.
Diffstat (limited to 'src')
-rw-r--r--src/backends/dialog.rs85
-rw-r--r--src/backends/mod.rs3
-rw-r--r--src/lib.rs83
3 files changed, 145 insertions, 26 deletions
diff --git a/src/backends/dialog.rs b/src/backends/dialog.rs
index d50be43..8cde8cc 100644
--- a/src/backends/dialog.rs
+++ b/src/backends/dialog.rs
@@ -5,7 +5,7 @@ use std::io;
use std::io::Result;
use std::process;
-use crate::Message;
+use crate::{Input, Message};
/// The `dialog` backend.
///
@@ -28,20 +28,6 @@ impl Dialog {
}
}
- fn execute(&self, args: Vec<&str>) -> Result<process::Output> {
- let mut args = args;
- if let Some(ref backtitle) = self.backtitle {
- args.insert(0, "--backtitle");
- args.insert(1, backtitle);
- }
- println!("{:?}", args);
- process::Command::new("dialog")
- .args(args)
- .stdin(process::Stdio::inherit())
- .stdout(process::Stdio::inherit())
- .output()
- }
-
/// Sets the backtitle for the dialog boxes.
///
/// The backtitle is displayed on the backdrop, at the top of the screen.
@@ -65,15 +51,31 @@ impl Dialog {
self.width = width.to_string();
}
- fn show_box(&self, args: Vec<&str>, title: &Option<String>) -> Result<process::Output> {
- let mut args = args;
+ fn execute(
+ &self,
+ args: Vec<&str>,
+ post_args: Vec<&str>,
+ title: &Option<String>,
+ ) -> Result<process::Output> {
+ let mut command = process::Command::new("dialog");
+ command.stdin(process::Stdio::inherit());
+ command.stdout(process::Stdio::inherit());
+
+ if let Some(ref backtitle) = self.backtitle {
+ command.arg("--backtitle");
+ command.arg(backtitle);
+ }
if let Some(ref title) = title {
- args.insert(0, "--title");
- args.insert(1, title);
+ command.arg("--title");
+ command.arg(title);
}
- args.push(&self.height);
- args.push(&self.width);
- self.execute(args)
+
+ command.args(args);
+ command.arg(&self.height);
+ command.arg(&self.width);
+ command.args(post_args);
+
+ command.output()
}
}
@@ -85,10 +87,47 @@ fn require_success(status: process::ExitStatus) -> Result<()> {
}
}
+fn get_stderr(output: process::Output) -> Result<Option<String>> {
+ if output.status.success() {
+ String::from_utf8(output.stderr)
+ .map(|s| Some(s))
+ .map_err(|_| {
+ io::Error::new(io::ErrorKind::Other, "Input contained invalid UTF-8 bytes")
+ })
+ } else {
+ if let Some(code) = output.status.code() {
+ match code {
+ 0 => Ok(None),
+ 1 => Ok(None),
+ -1 => Ok(None),
+ _ => Err(io::Error::new(
+ io::ErrorKind::Other,
+ "Could not execute dialog",
+ )),
+ }
+ } else {
+ Err(io::Error::new(
+ io::ErrorKind::Other,
+ "dialog was terminated by a signal",
+ ))
+ }
+ }
+}
+
impl super::Backend for Dialog {
+ fn show_input(&self, input: &Input) -> Result<Option<String>> {
+ let args = vec!["--inputbox", &input.text];
+ let mut post_args: Vec<&str> = Vec::new();
+ if let Some(ref default) = input.default {
+ post_args.push(default);
+ }
+ self.execute(args, post_args, &input.title)
+ .and_then(get_stderr)
+ }
+
fn show_message(&self, message: &Message) -> Result<()> {
let args = vec!["--msgbox", &message.text];
- self.show_box(args, &message.title)
+ self.execute(args, vec![], &message.title)
.and_then(|output| require_success(output.status))
.map(|_| ())
}
diff --git a/src/backends/mod.rs b/src/backends/mod.rs
index c316307..1abb8d1 100644
--- a/src/backends/mod.rs
+++ b/src/backends/mod.rs
@@ -17,6 +17,9 @@ use std::io::Result;
/// [`default_backend`]: ../function.default_backend.html
/// [`show_with`]: ../trait.DialogBox.html#method.show_with
pub trait Backend {
+ /// Shows the given input dialog and returns the input.
+ fn show_input(&self, input: &super::Input) -> Result<Option<String>>;
+
/// Shows the given message dialog.
fn show_message(&self, message: &super::Message) -> Result<()>;
}
diff --git a/src/lib.rs b/src/lib.rs
index 67bef2d..0c9d9f9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,6 +7,7 @@
//!
//! The `dialog` crate can be used to display different types of dialog boxes. The supported types
//! are:
+//! - [`Input`][]: a text input dialog
//! - [`Message`][]: a simple message box
//!
//! These dialog boxes can be displayed using various backends:
@@ -45,8 +46,24 @@
//! .expect("Could not display dialog box");
//! ```
//!
-//! [`Message`]: struct.Message.html
+//! Query a string from the user:
+//!
+//! ```no_run
+//! use dialog::DialogBox;
+//!
+//! let name = dialog::Input::new("Please enter your name")
+//! .title("Name")
+//! .show()
+//! .expect("Could not display dialog box");
+//! match name {
+//! Some(name) => println!("Hello {}!", name),
+//! None => println!("Hello stranger!"),
+//! };
+//! ```
+//!
//! [`Dialog`]: backends/struct.Dialog.html
+//! [`Input`]: struct.Input.html
+//! [`Message`]: struct.Message.html
//! [`default_backend`]: fn.default_backend.html
//! [`show`]: trait.DialogBox.html#method.show
//! [`show_with`]: trait.DialogBox.html#method.show_with
@@ -69,14 +86,14 @@ pub trait DialogBox {
/// The type of the data returned by the dialog box.
type Output;
- /// Shows this dialog box using the default backend.
+ /// Shows this dialog box using the default backend and returns the output.
///
/// `box.show()` is a shorthand for `box.show_with(&default_backend())`.
fn show(&self) -> Result<Self::Output> {
self.show_with(&default_backend())
}
- /// Shows this dialog box using the given backend.
+ /// Shows this dialog box using the given backend and returns the output.
fn show_with(&self, backend: &impl backends::Backend) -> Result<Self::Output>;
}
@@ -126,6 +143,66 @@ impl DialogBox for Message {
}
}
+/// A dialog box with a text input field.
+///
+/// This dialog box displays a text and an input field. It returns the text entered by the user or
+/// `None` if the user cancelled the dialog.
+///
+/// # Example
+///
+/// ```no_run
+/// use dialog::DialogBox;
+///
+/// let name = dialog::Input::new("Please enter your name")
+/// .title("Name")
+/// .show()
+/// .expect("Could not display dialog box");
+/// match name {
+/// Some(name) => println!("Hello {}!", name),
+/// None => println!("Hello stranger!"),
+/// };
+/// ```
+pub struct Input {
+ text: String,
+ title: Option<String>,
+ default: Option<String>,
+}
+
+impl Input {
+ /// Creates a new input dialog box with the given text.
+ pub fn new(text: impl Into<String>) -> Input {
+ Input {
+ text: text.into(),
+ title: None,
+ default: None,
+ }
+ }
+
+ /// Sets the title of this input box.
+ ///
+ /// This method returns a reference to `self` to enable chaining.
+ pub fn title(&mut self, title: impl Into<String>) -> &mut Input {
+ self.title = Some(title.into());
+ self
+ }
+
+ /// Sets the default value of this input box.
+ ///
+ /// This method returns a reference to `self` to enable chaining.
+ pub fn default(&mut self, default: impl Into<String>) -> &mut Input {
+ self.default = Some(default.into());
+ self
+ }
+}
+
+impl DialogBox for Input {
+ type Output = Option<String>;
+
+ fn show_with(&self, backend: &impl backends::Backend) -> Result<Self::Output> {
+ backend.show_input(self)
+ }
+}
+
/// Creates a new instance of the default backend.
///
/// The current implementation always returns a [`Dialog`][] instance.