aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2019-01-08 02:11:55 +0000
committerRobin Krahl <robin.krahl@ireas.org>2019-01-08 04:54:00 +0100
commita0b0a3c6a57097d56c5c471e5cb72bbde8198da8 (patch)
treebc91299159628f89594053aedcef3eaa71497413
parent31e1410bf3299301879171d5677f69925828844c (diff)
downloaddialog-rs-a0b0a3c6a57097d56c5c471e5cb72bbde8198da8.tar.gz
dialog-rs-a0b0a3c6a57097d56c5c471e5cb72bbde8198da8.tar.bz2
Implement message boxes using the dialog backend
This patch adds a first dialog box type, message boxes, and a first backend, the dialog(1) tool. It does not yet address the problems of output handling and backend selection.
-rw-r--r--src/backends/dialog.rs90
-rw-r--r--src/backends/mod.rs22
-rw-r--r--src/lib.rs134
-rw-r--r--tests/dialog.rs33
-rw-r--r--tests/message.rs18
5 files changed, 297 insertions, 0 deletions
diff --git a/src/backends/dialog.rs b/src/backends/dialog.rs
new file mode 100644
index 0000000..b448c05
--- /dev/null
+++ b/src/backends/dialog.rs
@@ -0,0 +1,90 @@
+// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
+// SPDX-License-Identifier: MIT
+
+use std::io;
+use std::io::Result;
+use std::process;
+
+use crate::Message;
+
+/// The `dialog` backend.
+///
+/// This backend uses the external `dialog` program (not to be confused with this crate also called
+/// `dialog`) to display text-based dialog boxes in the terminal.
+#[derive(Debug)]
+pub struct Dialog {
+ backtitle: Option<String>,
+ width: String,
+ height: String,
+}
+
+impl Dialog {
+ /// Creates a new `Dialog` instance without configuration.
+ pub fn new() -> Dialog {
+ Dialog {
+ backtitle: None,
+ height: "0".to_string(),
+ width: "0".to_string(),
+ }
+ }
+
+ fn execute(&self, args: Vec<&str>) -> Result<process::ExitStatus> {
+ 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).status()
+ }
+
+ /// Sets the backtitle for the dialog boxes.
+ ///
+ /// The backtitle is displayed on the backdrop, at the top of the screen.
+ pub fn set_backtitle(&mut self, backtitle: impl Into<String>) {
+ self.backtitle = Some(backtitle.into());
+ }
+
+ /// Sets the height of the dialog boxes.
+ ///
+ /// The height is given in characters. The actual height of the dialog box might be higher
+ /// than the given height if the content would not fit otherwise. The default height is zero.
+ pub fn set_height(&mut self, height: u32) {
+ self.height = height.to_string();
+ }
+
+ /// Sets the width of the dialog boxes.
+ ///
+ /// The width is given in characters. The actual width of the dialog box might be higher than
+ /// the given width if the content would not fit otherwise. The default width is zero.
+ pub fn set_width(&mut self, width: u32) {
+ self.width = width.to_string();
+ }
+
+ fn show_box(&self, args: Vec<&str>, title: &Option<String>) -> Result<process::ExitStatus> {
+ let mut args = args;
+ if let Some(ref title) = title {
+ args.insert(0, "--title");
+ args.insert(1, title);
+ }
+ args.push(&self.height);
+ args.push(&self.width);
+ self.execute(args)
+ }
+}
+
+fn require_success(status: process::ExitStatus) -> Result<()> {
+ if status.success() {
+ Ok(())
+ } else {
+ Err(io::Error::new(io::ErrorKind::Other, "dialog failed"))
+ }
+}
+
+impl super::Backend for Dialog {
+ fn show_message(&self, message: &Message) -> Result<()> {
+ let args = vec!["--msgbox", &message.text];
+ self.show_box(args, &message.title)
+ .and_then(require_success)
+ }
+}
diff --git a/src/backends/mod.rs b/src/backends/mod.rs
new file mode 100644
index 0000000..c316307
--- /dev/null
+++ b/src/backends/mod.rs
@@ -0,0 +1,22 @@
+// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
+// SPDX-License-Identifier: MIT
+
+mod dialog;
+
+pub use crate::backends::dialog::Dialog;
+
+use std::io::Result;
+
+/// A dialog backend.
+///
+/// A dialog backend is a program that can be used to display dialog boxes. Use the
+/// [`default_backend`][] function to create a new instance of the default backend, or choose a
+/// backend and create an instance manually. To use a backend, pass it to the [`show_with`][]
+/// method of a dialog box.
+///
+/// [`default_backend`]: ../function.default_backend.html
+/// [`show_with`]: ../trait.DialogBox.html#method.show_with
+pub trait Backend {
+ /// Shows the given message dialog.
+ fn show_message(&self, message: &super::Message) -> Result<()>;
+}
diff --git a/src/lib.rs b/src/lib.rs
index fd09812..67bef2d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,2 +1,136 @@
// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
+
+#![warn(missing_docs, rust_2018_compatibility, rust_2018_idioms, unused)]
+
+//! Displays dialog boxes using various backends.
+//!
+//! The `dialog` crate can be used to display different types of dialog boxes. The supported types
+//! are:
+//! - [`Message`][]: a simple message box
+//!
+//! These dialog boxes can be displayed using various backends:
+//! - [`Dialog`][]: uses `dialog` to display ncurses-based dialog boxes (requires the external
+//! `dialog` tool)
+//!
+//! You can let `dialog` choose the backend by calling the [`show`][] method on a dialog box. If
+//! you want to choose the backend yourself, create a backend instance and pass it to
+//! [`show_with`][]. You can also use the [`default_backend`][] function to create a backend.
+//!
+//! # Examples
+//!
+//! Show a message box using the default backend:
+//!
+//! ```no_run
+//! use dialog::DialogBox;
+//!
+//! dialog::Message::new("Did you know that I am using the dialog crate?")
+//! .title("Public Service Announcement")
+//! .show()
+//! .expect("Could not display dialog box");
+//! ```
+//!
+//! Show a message box using the [`Dialog`][] backend with customized settings:
+//!
+//! ```no_run
+//! use dialog::DialogBox;
+//!
+//! let mut backend = dialog::backends::Dialog::new();
+//! backend.set_backtitle("dialog demo");
+//! backend.set_width(100);
+//! backend.set_height(10);
+//! dialog::Message::new("Did you know that I am using the dialog crate?")
+//! .title("Public Service Announcement")
+//! .show_with(&backend)
+//! .expect("Could not display dialog box");
+//! ```
+//!
+//! [`Message`]: struct.Message.html
+//! [`Dialog`]: backends/struct.Dialog.html
+//! [`default_backend`]: fn.default_backend.html
+//! [`show`]: trait.DialogBox.html#method.show
+//! [`show_with`]: trait.DialogBox.html#method.show_with
+
+/// Backends that display dialog boxes.
+///
+/// All backends implement the [`Backend`][] trait. Some backends might provide additional
+/// settings. For a list of supported backends, see the [top-level crate documentation](./..) or
+/// the [list of structs in this module](#structs).
+///
+/// [`Backend`]: trait.Backend.html
+pub mod backends;
+
+use std::io::Result;
+
+/// A dialog box that can be shown using a backend.
+///
+/// Some dialog boxes might return data of the type `Output`.
+pub trait DialogBox {
+ /// The type of the data returned by the dialog box.
+ type Output;
+
+ /// Shows this dialog box using the default backend.
+ ///
+ /// `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.
+ fn show_with(&self, backend: &impl backends::Backend) -> Result<Self::Output>;
+}
+
+/// A message box.
+///
+/// This dialog box displays a text and an optional title and has a single OK button. It does not
+/// produce any output.
+///
+/// # Example
+///
+/// ```no_run
+/// use dialog::DialogBox;
+///
+/// dialog::Message::new("The operation was successful.")
+/// .title("Success")
+/// .show()
+/// .expect("Could not display dialog box");
+/// ```
+pub struct Message {
+ text: String,
+ title: Option<String>,
+}
+
+impl Message {
+ /// Creates a new message box with the given text.
+ pub fn new(text: impl Into<String>) -> Message {
+ Message {
+ text: text.into(),
+ title: None,
+ }
+ }
+
+ /// Sets the title of this message box.
+ ///
+ /// This method returns a reference to `self` to enable chaining.
+ pub fn title(&mut self, title: impl Into<String>) -> &mut Message {
+ self.title = Some(title.into());
+ self
+ }
+}
+
+impl DialogBox for Message {
+ type Output = ();
+
+ fn show_with(&self, backend: &impl backends::Backend) -> Result<Self::Output> {
+ backend.show_message(self)
+ }
+}
+
+/// Creates a new instance of the default backend.
+///
+/// The current implementation always returns a [`Dialog`][] instance.
+///
+/// [`Dialog`]: backends/struct.Dialog.html
+pub fn default_backend() -> impl backends::Backend {
+ backends::Dialog::new()
+}
diff --git a/tests/dialog.rs b/tests/dialog.rs
new file mode 100644
index 0000000..4c671f8
--- /dev/null
+++ b/tests/dialog.rs
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
+// SPDX-License-Identifier: MIT
+
+use std::io::Result;
+
+use dialog::backends;
+use dialog::DialogBox;
+
+#[test]
+fn message() -> Result<()> {
+ dialog::Message::new("This is a message.")
+ .title("And this is a title:")
+ .show_with(&backends::Dialog::new())
+}
+
+#[test]
+fn backtitle() -> Result<()> {
+ let mut backend = backends::Dialog::new();
+ backend.set_backtitle("Backtitle");
+ dialog::Message::new("This is a message.")
+ .title("And this is a title:")
+ .show_with(&backend)
+}
+
+#[test]
+fn size() -> Result<()> {
+ let mut backend = backends::Dialog::new();
+ backend.set_width(100);
+ backend.set_height(10);
+ dialog::Message::new("This is a message.")
+ .title("And this is a title:")
+ .show_with(&backend)
+}
diff --git a/tests/message.rs b/tests/message.rs
new file mode 100644
index 0000000..4b1f18c
--- /dev/null
+++ b/tests/message.rs
@@ -0,0 +1,18 @@
+// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
+// SPDX-License-Identifier: MIT
+
+use std::io::Result;
+
+use dialog::DialogBox;
+
+#[test]
+fn text() -> Result<()> {
+ dialog::Message::new("This is a message.").show()
+}
+
+#[test]
+fn text_title() -> Result<()> {
+ dialog::Message::new("This is a message.")
+ .title("And this is a title:")
+ .show()
+}