Skip to content

Commit

Permalink
feat(prompts): implement number prompt, slightly restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
norskeld committed Mar 10, 2024
1 parent 674f0bb commit 51ff257
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 36 deletions.
6 changes: 4 additions & 2 deletions src/actions/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ use tokio::fs::{self, File, OpenOptions};
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use unindent::Unindent;

use crate::actions::{State, Value};
use crate::actions::State;
use crate::config::actions::*;
use crate::config::value::*;
use crate::path::{PathClean, Traverser};
use crate::spinner::Spinner;

Expand Down Expand Up @@ -274,9 +275,10 @@ impl Prompt {
pub async fn execute(&self, state: &mut State) -> miette::Result<()> {
match self {
| Self::Confirm(prompt) => prompt.execute(state).await,
| Self::Editor(prompt) => prompt.execute(state).await,
| Self::Input(prompt) => prompt.execute(state).await,
| Self::Number(prompt) => prompt.execute(state).await,
| Self::Select(prompt) => prompt.execute(state).await,
| Self::Editor(prompt) => prompt.execute(state).await,
}
}
}
Expand Down
11 changes: 1 addition & 10 deletions src/actions/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use miette::Diagnostic;
use thiserror::Error;
use tokio::fs;

use crate::config::{ActionSingle, ActionSuite, Actions, Config};
use crate::config::{ActionSingle, ActionSuite, Actions, Config, Value};

#[derive(Debug, Diagnostic, Error)]
pub enum ExecutorError {
Expand All @@ -19,15 +19,6 @@ pub enum ExecutorError {
},
}

/// Replacement value.
#[derive(Debug)]
pub enum Value {
/// A string value.
String(String),
/// A boolean value.
Bool(bool),
}

#[derive(Debug)]
pub struct State {
/// A map of replacements and associated values.
Expand Down
52 changes: 40 additions & 12 deletions src/actions/prompts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ use std::process;

use crossterm::style::Stylize;
use inquire::formatter::StringFormatter;
use inquire::required;
use inquire::ui::{Color, RenderConfig, StyleSheet, Styled};
use inquire::{required, CustomType};
use inquire::{Confirm, Editor, InquireError, Select, Text};

use crate::actions::{State, Value};
use crate::config::prompts;
use crate::actions::State;
use crate::config::prompts::*;
use crate::config::{Number, Value};

/// Helper module holding useful functions.
mod helpers {
Expand Down Expand Up @@ -52,7 +53,7 @@ mod helpers {
}

/// Handle interruption/cancelation events.
pub fn handle_interruption(err: InquireError) {
pub fn interrupt(err: InquireError) {
match err {
| InquireError::OperationCanceled => {
process::exit(0);
Expand All @@ -66,7 +67,7 @@ mod helpers {
}
}

impl prompts::Confirm {
impl ConfirmPrompt {
/// Execute the prompt and populate the state.
pub async fn execute(&self, state: &mut State) -> miette::Result<()> {
let (name, hint, help) = helpers::messages(&self.name, &self.hint);
Expand All @@ -81,14 +82,14 @@ impl prompts::Confirm {

match prompt.prompt() {
| Ok(value) => state.set(name, Value::Bool(value)),
| Err(err) => helpers::handle_interruption(err),
| Err(err) => helpers::interrupt(err),
}

Ok(())
}
}

impl prompts::Input {
impl InputPrompt {
/// Execute the prompt and populate the state.
pub async fn execute(&self, state: &mut State) -> miette::Result<()> {
let (name, hint, help) = helpers::messages(&self.name, &self.hint);
Expand All @@ -106,14 +107,41 @@ impl prompts::Input {

match prompt.prompt() {
| Ok(value) => state.set(name, Value::String(value)),
| Err(err) => helpers::handle_interruption(err),
| Err(err) => helpers::interrupt(err),
}

Ok(())
}
}

impl prompts::Select {
impl NumberPrompt {
/// Execute the prompt and populate the state.
pub async fn execute(&self, state: &mut State) -> miette::Result<()> {
let (name, hint, help) = helpers::messages(&self.name, &self.hint);

let mut prompt = CustomType::<Number>::new(&hint)
.with_help_message(&help)
.with_formatter(&|input| input.to_string())
.with_render_config(helpers::theme());

if let Some(default) = &self.default {
prompt = prompt.with_default(default.to_owned());
} else {
// NOTE: This is a bit confusing, but essentially this message will be showed when no input
// was provided by the user.
prompt = prompt.with_error_message("This field is required.");
}

match prompt.prompt() {
| Ok(value) => state.set(name, Value::Number(value)),
| Err(err) => helpers::interrupt(err),
}

Ok(())
}
}

impl SelectPrompt {
/// Execute the prompt and populate the state.
pub async fn execute(&self, state: &mut State) -> miette::Result<()> {
let (name, hint, help) = helpers::messages(&self.name, &self.hint);
Expand All @@ -126,14 +154,14 @@ impl prompts::Select {

match prompt.prompt() {
| Ok(value) => state.set(name, Value::String(value)),
| Err(err) => helpers::handle_interruption(err),
| Err(err) => helpers::interrupt(err),
}

Ok(())
}
}

impl prompts::Editor {
impl EditorPrompt {
/// Execute the prompt and populate the state.
pub async fn execute(&self, state: &mut State) -> miette::Result<()> {
let (name, hint, help) = helpers::messages(&self.name, &self.hint);
Expand All @@ -148,7 +176,7 @@ impl prompts::Editor {

match prompt.prompt() {
| Ok(value) => state.set(name, Value::String(value)),
| Err(err) => helpers::handle_interruption(err),
| Err(err) => helpers::interrupt(err),
}

Ok(())
Expand Down
9 changes: 5 additions & 4 deletions src/config/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ pub struct Run {
/// Prompt actions.
#[derive(Debug)]
pub enum Prompt {
Input(Input),
Select(Select),
Confirm(Confirm),
Editor(Editor),
Input(InputPrompt),
Number(NumberPrompt),
Select(SelectPrompt),
Confirm(ConfirmPrompt),
Editor(EditorPrompt),
}

/// Execute given replacements using values provided by prompts. Optionally, only apply
Expand Down
22 changes: 18 additions & 4 deletions src/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use thiserror::Error;

use crate::config::actions::*;
use crate::config::prompts::*;
use crate::config::value::*;
use crate::config::KdlUtils;

const CONFIG_NAME: &str = "arx.kdl";
Expand Down Expand Up @@ -371,16 +372,25 @@ impl Config {
| "input" => {
let nodes = self.get_children(node, vec!["hint"])?;

ActionSingle::Prompt(Prompt::Input(Input {
ActionSingle::Prompt(Prompt::Input(InputPrompt {
name: self.get_arg_string(node)?,
hint: self.get_hint(node, nodes)?,
default: self.get_default_string(nodes),
}))
},
| "number" => {
let nodes = self.get_children(node, vec!["hint"])?;

ActionSingle::Prompt(Prompt::Number(NumberPrompt {
name: self.get_arg_string(node)?,
hint: self.get_hint(node, nodes)?,
default: self.get_default_number(nodes),
}))
},
| "editor" => {
let nodes = self.get_children(node, vec!["hint"])?;

ActionSingle::Prompt(Prompt::Editor(Editor {
ActionSingle::Prompt(Prompt::Editor(EditorPrompt {
name: self.get_arg_string(node)?,
hint: self.get_hint(node, nodes)?,
default: self.get_default_string(nodes),
Expand All @@ -389,7 +399,7 @@ impl Config {
| "select" => {
let nodes = self.get_children(node, vec!["hint", "options"])?;

ActionSingle::Prompt(Prompt::Select(Select {
ActionSingle::Prompt(Prompt::Select(SelectPrompt {
name: self.get_arg_string(node)?,
hint: self.get_hint(node, nodes)?,
options: self.get_options(node, nodes)?,
Expand All @@ -398,7 +408,7 @@ impl Config {
| "confirm" => {
let nodes = self.get_children(node, vec!["hint"])?;

ActionSingle::Prompt(Prompt::Confirm(Confirm {
ActionSingle::Prompt(Prompt::Confirm(ConfirmPrompt {
name: self.get_arg_string(node)?,
hint: self.get_hint(node, nodes)?,
default: self.get_default_bool(nodes),
Expand Down Expand Up @@ -563,4 +573,8 @@ impl Config {
fn get_default_bool(&self, nodes: &KdlDocument) -> Option<bool> {
nodes.get("default").and_then(|node| node.get_bool(0))
}

fn get_default_number(&self, nodes: &KdlDocument) -> Option<Number> {
nodes.get("default").and_then(|node| node.get_number(0))
}
}
2 changes: 2 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
pub use config::*;
pub use utils::*;
pub use value::*;

pub mod actions;
pub mod prompts;
pub mod value;

mod config;
mod utils;
20 changes: 16 additions & 4 deletions src/config/prompts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::config::value::Number;

#[derive(Debug)]
pub struct Input {
pub struct InputPrompt {
/// Name of the variable that will store the answer.
pub name: String,
/// Short description.
Expand All @@ -9,7 +11,17 @@ pub struct Input {
}

#[derive(Debug)]
pub struct Select {
pub struct NumberPrompt {
/// Name of the variable that will store the answer.
pub name: String,
/// Short description.
pub hint: String,
/// Default value if input is empty.
pub default: Option<Number>,
}

#[derive(Debug)]
pub struct SelectPrompt {
/// Name of the variable that will store the answer.
pub name: String,
/// Short description.
Expand All @@ -19,7 +31,7 @@ pub struct Select {
}

#[derive(Debug)]
pub struct Confirm {
pub struct ConfirmPrompt {
/// Name of the variable that will store the answer.
pub name: String,
/// Short description of the prompt.
Expand All @@ -29,7 +41,7 @@ pub struct Confirm {
}

#[derive(Debug)]
pub struct Editor {
pub struct EditorPrompt {
/// Name of the variable that will store the answer.
pub name: String,
/// Short description.
Expand Down
16 changes: 16 additions & 0 deletions src/config/utils.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use kdl::{KdlNode, NodeKey};

use crate::config::Number;

pub trait KdlUtils<K> {
/// Gets an entry by key and tries to map to a [String].
fn get_string(&self, key: K) -> Option<String>;

/// Gets an entry by key and tries to map it to a [NumberValue].
fn get_number(&self, key: K) -> Option<Number>;

/// Gets an entry by key and tries to map it to a [bool].
fn get_bool(&self, key: K) -> Option<bool>;
}
Expand All @@ -18,6 +23,17 @@ where
.and_then(|entry| entry.value().as_string().map(str::to_string))
}

fn get_number(&self, key: K) -> Option<Number> {
self.get(key).and_then(|entry| {
let value = entry.value();

value
.as_i64()
.map(Number::Integer)
.or_else(|| value.as_f64().map(Number::Float))
})
}

fn get_bool(&self, key: K) -> Option<bool> {
self.get(key).and_then(|entry| entry.value().as_bool())
}
Expand Down
50 changes: 50 additions & 0 deletions src/config/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::fmt::{self, Display};
use std::str::FromStr;

use miette::Diagnostic;
use thiserror::Error;

#[derive(Debug, Diagnostic, Error)]
#[error("`{0}` is not a valid number.")]
#[diagnostic(code(arx::config::prompts::parse))]
pub struct NumberParseError(pub String);

/// Value of a number prompt.
#[derive(Clone, Debug)]
pub enum Number {
/// Integer value.
Integer(i64),
/// Floating point value.
Float(f64),
}

impl Display for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
| Self::Integer(int) => write!(f, "{int}"),
| Self::Float(float) => write!(f, "{float}"),
}
}
}

impl FromStr for Number {
type Err = NumberParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<i64>()
.map(Self::Integer)
.or_else(|_| s.parse::<f64>().map(Self::Float))
.map_err(|_| NumberParseError(s.to_string()))
}
}

/// Replacement value.
#[derive(Debug)]
pub enum Value {
/// A string value.
String(String),
// A number value.
Number(Number),
/// A boolean value.
Bool(bool),
}

0 comments on commit 51ff257

Please sign in to comment.