aboutsummaryrefslogtreecommitdiff
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
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.
-rw-r--r--examples/input.rs22
-rw-r--r--src/backends/dialog.rs85
-rw-r--r--src/backends/mod.rs3
-rw-r--r--src/lib.rs83
4 files changed, 167 insertions, 26 deletions
diff --git a/examples/input.rs b/examples/input.rs
new file mode 100644
index 0000000..bb36fb6
--- /dev/null
+++ b/examples/input.rs
@@ -0,0 +1,22 @@
+// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
+// SPDX-License-Identifier: MIT
+
+use std::io::Result;
+
+use dialog::DialogBox;
+
+fn main() -> Result<()> {
+ let input1 = dialog::Input::new("Please enter something").show()?;
+ let input2 = dialog::Input::new("Please enter something")
+ .title("Input form")
+ .show()?;
+ let input3 = dialog::Input::new("Please enter something with a default")
+ .title("Input form")
+ .default("input")
+ .show()?;
+
+ println!("Input 1: {:?}", input1);
+ println!("Input 2: {:?}", input2);
+ println!("Input 3: {:?}", input3);
+ Ok(())
+}
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.