diff options
| -rw-r--r-- | examples/input.rs | 22 | ||||
| -rw-r--r-- | src/backends/dialog.rs | 85 | ||||
| -rw-r--r-- | src/backends/mod.rs | 3 | ||||
| -rw-r--r-- | src/lib.rs | 83 | 
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<()>;  } @@ -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. | 
